我知道,在.NET结构不支持继承,但它并不完全清楚为什么他们以这种方式限制。
什么技术原因,防止结构从其它结构继承?
我知道,在.NET结构不支持继承,但它并不完全清楚为什么他们以这种方式限制。
什么技术原因,防止结构从其它结构继承?
究其原因值类型不能支持继承是因为阵列。
问题是,对于性能和GC的原因,值类型的数组存储“内联”。 例如,给定new FooType[10] {...}
如果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);
通过正常的转换规则,一个Derived[]
可转化为Base[]
更好或更差),因此,如果S /结构/类/ 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;
}
(是的,这可恶的组装,但问题是,我们将通过在已知的编译时间常量数组递增,无需正在使用派生类型的任何知识。)
所以,如果这真的发生,我们就会有内存损坏问题。 具体而言,内Square()
values[1].A*=2
将修改values[0].B
!
尝试调试 !
试想一下,结构支持继承。 然后宣布:
BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.
a = b; //?? expand size during assignment?
将意味着结构变量没有固定的大小,这就是为什么我们有引用类型。
更妙的是,考虑一下:
BaseStruct[] baseArray = new BaseStruct[1000];
baseArray[500] = new InheritedStruct(); //?? morph/resize the array?
结构不使用引用(除非它们被装箱,但你应该尽量避免),从而由于是通过引用指针没有间接多态性是没有意义的。 对象通常生活在堆上并经由参考指针引用,但结构是在栈上分配(除非它们被框)或分配“内部”通过在堆上引用类型占用的存储器。
下面是该文档说:
结构对于具有值语义小的数据结构是特别有用的。 复数,坐标系中的点或字典中的键值对都是结构的典型示例。 关键是这些数据结构是,他们有几个数据成员,他们不要求使用继承或引用身份,他们可以使用值语义可以方便地实现,其中分配值复制,而不是参考。
基本上,他们应该保持简单的数据,因此没有“额外的功能”,如继承。 或许,这在技术上是可行的他们支持一些有限的一种传承(不多态性,因为他们是在栈上),但我相信它也是一个设计选择不支持继承(如.NET许多其他的事情语言。)
在另一方面,我同意继承的好处,我想我们都打,我们希望我们的点struct
从另一个继承,并意识到这是不可能的。 但在这一点上,数据结构可能是如此先进,它应该是一个类反正。
如继承类是不可能的,因为一个结构被直接铺设在堆栈中。 一个继承结构会更大然后是父母,但JIT不知道的话,并试图把太多的太多的空间较小。 听起来有点不清楚,让我们写一个例子:
struct A {
int property;
} // sizeof A == sizeof int
struct B : A {
int childproperty;
} // sizeof B == sizeof int * 2
如果这是可能的,它会崩溃在下面的代码片段:
void DoSomething(A arg){};
...
B b;
DoSomething(b);
空间分配给的sizeof A,不是为B.的sizeof
有一点我想纠正。 即使理由结构不能被继承,是因为他们生活在堆栈上是正确的,它是在同一个半正确的解释。 结构,就像任何其他的值类型可以住在堆。 因为这将取决于对变量的声明,他们要么住在堆栈或堆中 。 当他们分别是局部变量或实例字段这将是。
在说,塞西尔有一个名字正确地钉它。
我想强调这一点,值类型可以住在堆栈上。 这并不意味着他们总是这样做。 局部变量,包括方法的参数,会。 所有其他的不会。 尽管如此,它仍然不能被继承的原因。 :-)
结构是在栈中分配。 这意味着值语义是非常自由的,访问结构成员是非常便宜的。 这并不妨碍多态性。
你可以有每个结构的指针其虚函数表开始。 这将是一个性能问题(每结构将是一个指针的至少大小),但它是可行的。 这将允许虚拟功能。
怎么样添加字段?
那么,当你在栈上分配结构,你分配了一定的空间。 所需的空间是在编译时(时间提前或时是否JITting)测定。 如果添加字段,然后分配给一个基本类型:
struct A
{
public int Integer1;
}
struct B : A
{
public int Integer2;
}
A a = new B();
这将覆盖堆栈的一些未知的一部分。
另一种方法是用于运行时防止这种由只写的sizeof(A)字节到任何的变量。
当B会覆盖的方法,并引用其整数2场会发生什么? 无论是运行时会引发MemberAccessException,或方法,而不是访问堆栈上一些随机数据。 这些都不是允许的。
这是完全安全的,有结构的继承,只要你不使用结构多态,或者只要你不添加继承时域。 但这些都不是非常有用的。
这似乎是一个很常见的问题。 我觉得增加值类型存储“到位”,您声明变量; 除了实现细节,这意味着没有对象标题,说一些有关的对象, 只有可变知道什么样的数据驻留在那里。
结构做支持的接口,这样你就可以做一些事情多态性这种方式。
IL是基于堆栈的语言,所以调用方法与参数是这样的:
当方法运行时,它会弹出堆栈一些字节来获得它的参数。 它确切地知道多少字节爆开,因为参数是任一个的提及类型的指针(在32位总是4个字节),或者它是一个值类型的量,大小始终精确地已知的。
如果它是一个引用类型指针,则该方法中查找对象在堆和获得它的类型手柄,它指向该句柄类型完全相同该特定方法的方法表。 如果是值类型,则没有查找到的方法表是必要的,因为值类型不支持继承,所以只有一个可能的方法/类型组合。
如果值类型支持继承则会有额外的开销,该结构的特定类型将不得不放置在栈上,以及它的价值,这将意味着某种方法查找表中类型的特定的具体实例。 这将消除值类型的速度和效率优势。