为什么结构不支持继承?

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,它将按预期编译和运行,没有任何问题.但是如果BaseDerived是值类型,并且数组存储内联值,那么我们就会遇到问题.

我们有一个问题因为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!

尝试调试那个!

  • 对这个问题的明智解决方案是禁止使用基础[]到Detived [].就像禁止从short []到int []的强制转换一样,尽管可以从short转换为int. (10认同)
  • 是的,但这"有意义",因为数组转换是针对隐式转换,而不是显式转换.short to int是可能的,但是需要强制转换,所以short []不能转换为int []是明智的(缺少转换代码,比如'a.Select(x =>(int)x).ToArray( )").如果运行时不允许从Base转换为Derived,那么它将是一个"wart",因为IS允许引用类型.所以我们有两种不同的"疣"可能 - 禁止结构继承或禁止将派生数组转换为基数数组. (4认同)
  • +回答:继承问题没有点击我,直到你把它放在数组.另一位用户表示,通过将结构"切片"到适当的大小可以减轻这个问题,但我认为切片是导致问题多于解决的问题. (3认同)
  • 至少通过阻止结构继承,我们有一个单独的关键字,可以更容易地说"结构是特殊的",而不是对一组东西(类)有效但对另一组(结构)有效的"随机"限制. .我想结构限制更容易解释("它们是不同的!"). (3认同)
  • 你是对的,那是可怕的集会。事实上,我认为你需要一个 C 编译器来编译它;) (2认同)
  • 需要将函数名称从'square'更改为'double' (2认同)

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)

  • C++通过引入"切片"的概念来回答这个问题,因此这是一个可解决的问题.那么,为什么不支持结构继承呢? (5认同)
  • @jonp:可以解决,是的.可取?这是一个思想实验:想象一下,如果你有一个基类Vector2D(x,y)和派生类Vector3D(x,y,z).两个类都有一个Magnitude属性,它分别计算sqrt(x ^ 2 + y ^ 2)和sqrt(x ^ 2 + y ^ 2 + z ^ 2).如果你写'Vector3D a = Vector3D(5,10,15); Vector2D b = a;','a.Magnitude == b.Magnitude'应该返回什么?如果我们然后写'a =(Vector3D)b',a.Magnitude在赋值之前是否具有与之后相同的值?.NET设计师可能会对自己说:"不,我们没有那个". (4认同)
  • 考虑可继承结构的数组,并记住 C# 是一种(内存)管理语言。切片或任何类似的选项都会对 CLR 的基础造成严重破坏。 (2认同)

Mar*_*age 14

结构不使用引用(除非它们是盒装的,但你应该试着避免这种情况)因此多态性没有意义,因为没有通过引用指针的间接.对象通常生活在堆上并经由参考指针引用,但结构是在栈上分配(除非它们被框)或分配"内部"通过在堆上引用类型占用的存储器.

  • 一个人不需要使用多态来利用继承 (7认同)

Bli*_*ixt 8

以下是文档所说的内容:

结构对于具有值语义的小型数据结构特别有用.复数,坐标系中的点或字典中的键值对都是结构的好例子.关键是这些数据结构是,他们有几个数据成员,他们不要求使用继承或引用身份,他们可以使用值语义可以方便地实现,其中分配值复制,而不是参考.

基本上,它们应该包含简单数据,因此不具有继承等"额外功能".它们在技术上可能支持一些有限类型的继承(不是多态,因为它们在堆栈中),但我相信它也是不支持继承的设计选择(正如.NET中的许多其他东西一样)语言是.)

另一方面,我同意继承的好处,我认为我们都希望我们struct从另一个继承,并意识到这是不可能的.但在那时,数据结构可能非常先进,无论如何它应该是一个类.

  • 这不是没有继承的原因. (4认同)
  • 不回答这个问题. (4认同)

Dyk*_*kam 7

类继承是不可能的,因为结构直接放在堆栈上。继承结构会比它的父结构大,但 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 的大小。

  • C++ 可以很好地处理这种情况,IIRC。B 的实例被切片以适应 A 的大小。如果它是纯数据类型,如 .NET 结构,那么不会发生任何不好的事情。您确实遇到了一个返回 A 的方法的问题,并且您将该返回值存储在 B 中,但这不应该被允许。简而言之,.NET 设计人员_可以_处理这个问题,如果他们愿意的话,但出于某种原因他们没有。 (2认同)
  • @John:我没有断言它是,我认为@jonp 也不是。我们只是提到这个问题很老并且已经解决了,所以 .NET 设计者选择不支持它是因为技术上的不可行性。 (2认同)

use*_*001 5

结构在堆栈上分配。这意味着值语义几乎是免费的,并且访问结构成员非常便宜。这并不能阻止多态性。

您可以让每个结构以指向其虚函数表的指针开始。这将是一个性能问题(每个结构至少是一个指针的大小),但它是可行的。这将允许虚拟功能。

添加字段怎么样?

好吧,当您在堆栈上分配结构时,您分配了一定数量的空间。所需的空间是在编译时确定的(无论是提前还是在 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 异常,或者该方法访问堆栈上的一些随机数据。这些都是不允许的。

结构继承是完全安全的,只要您不以多态方式使用结构,或者只要您在继承时不添加字段。但这些都不是很有用。

  • 几乎。没有其他人提到关于堆栈的切片问题,只提到数组。没有其他人提到可用的解决方案。 (2认同)