Jul*_*iet 120 .net inheritance struct
我知道.NET中的结构不支持继承,但不清楚为什么它们以这种方式受限.
什么技术原因阻止结构继承其他结构?
jon*_*onp 119
原因值类型不支持继承是因为数组.
问题是,出于性能和GC的原因,值类型的数组存储在"内联"中.例如,给定new FooType[10] {...}
if,如果FooType
是引用类型,将在托管堆上创建11个对象(一个用于数组,10个用于每个类型实例).如果FooType
是值类型,则只在托管堆上创建一个实例 - 对于数组本身(因为每个数组值将与数组一起"内联"存储).
现在,假设我们有值类型的继承.当与数组的上述"内联存储"行为相结合时,坏事就会发生,就像在C++中看到的那样.
考虑这个伪C#代码:
struct Base
{
public int A;
}
struct Derived : Base
{
public int B;
}
void Square(Base[] values)
{
for (int i = 0; i < values.Length; ++i)
values [i].A *= 2;
}
Derived[] v = new Derived[2];
Square (v);
Run Code Online (Sandbox Code Playgroud)
通过正常的转换规则,a Derived[]
可以转换为a Base[]
(无论好坏),所以如果您使用上面示例的s/struct/class/g,它将按预期编译和运行,没有任何问题.但是如果Base
和Derived
是值类型,并且数组存储内联值,那么我们就会遇到问题.
我们有一个问题因为Square()
不知道任何事情Derived
,它只使用指针算法来访问数组的每个元素,递增一个常量(sizeof(A)
).大会模糊地说:
for (int i = 0; i < values.Length; ++i)
{
A* value = (A*) (((char*) values) + i * sizeof(A));
value->A *= 2;
}
Run Code Online (Sandbox Code Playgroud)
(是的,这是令人讨厌的汇编,但重点是我们将在已知的编译时常量中递增数组,而不知道正在使用派生类型.)
所以,如果这确实发生了,我们就会遇到内存损坏问题.具体而言,内Square()
,values[1].A*=2
会实际上是修改values[0].B
!
尝试调试那个!
Ken*_* K. 68
想象一下结构支持继承.然后声明:
BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.
a = b; //?? expand size during assignment?
Run Code Online (Sandbox Code Playgroud)
意味着结构变量没有固定的大小,这就是我们有引用类型的原因.
更好的是,考虑一下:
BaseStruct[] baseArray = new BaseStruct[1000];
baseArray[500] = new InheritedStruct(); //?? morph/resize the array?
Run Code Online (Sandbox Code Playgroud)
Mar*_*age 14
结构不使用引用(除非它们是盒装的,但你应该试着避免这种情况)因此多态性没有意义,因为没有通过引用指针的间接.对象通常生活在堆上并经由参考指针引用,但结构是在栈上分配(除非它们被框)或分配"内部"通过在堆上引用类型占用的存储器.
以下是文档所说的内容:
结构对于具有值语义的小型数据结构特别有用.复数,坐标系中的点或字典中的键值对都是结构的好例子.关键是这些数据结构是,他们有几个数据成员,他们不要求使用继承或引用身份,他们可以使用值语义可以方便地实现,其中分配值复制,而不是参考.
基本上,它们应该包含简单数据,因此不具有继承等"额外功能".它们在技术上可能支持一些有限类型的继承(不是多态,因为它们在堆栈中),但我相信它也是不支持继承的设计选择(正如.NET中的许多其他东西一样)语言是.)
另一方面,我同意继承的好处,我认为我们都希望我们struct
从另一个继承,并意识到这是不可能的.但在那时,数据结构可能非常先进,无论如何它应该是一个类.
类继承是不可能的,因为结构直接放在堆栈上。继承结构会比它的父结构大,但 JIT 不知道这一点,并试图在太少的空间上放置太多。听起来有点不清楚,我们来写一个例子:
struct A {
int property;
} // sizeof A == sizeof int
struct B : A {
int childproperty;
} // sizeof B == sizeof int * 2
Run Code Online (Sandbox Code Playgroud)
如果这是可能的,它将在以下代码段上崩溃:
void DoSomething(A arg){};
...
B b;
DoSomething(b);
Run Code Online (Sandbox Code Playgroud)
空间分配给 A 的大小,而不是 B 的大小。
结构在堆栈上分配。这意味着值语义几乎是免费的,并且访问结构成员非常便宜。这并不能阻止多态性。
您可以让每个结构以指向其虚函数表的指针开始。这将是一个性能问题(每个结构至少是一个指针的大小),但它是可行的。这将允许虚拟功能。
添加字段怎么样?
好吧,当您在堆栈上分配结构时,您分配了一定数量的空间。所需的空间是在编译时确定的(无论是提前还是在 JITting 时)。如果添加字段然后分配给基本类型:
struct A
{
public int Integer1;
}
struct B : A
{
public int Integer2;
}
A a = new B();
Run Code Online (Sandbox Code Playgroud)
这将覆盖堆栈的某些未知部分。
另一种方法是让运行时通过仅将 sizeof(A) 字节写入任何 A 变量来防止这种情况发生。
如果 B 覆盖 A 中的方法并引用其 Integer2 字段会发生什么?运行时抛出 MemberAccessException 异常,或者该方法访问堆栈上的一些随机数据。这些都是不允许的。
结构继承是完全安全的,只要您不以多态方式使用结构,或者只要您在继承时不添加字段。但这些都不是很有用。