.net 为什么结构不支持继承?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1222935/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 13:06:19  来源:igfitidea点击:

Why don't structs support inheritance?

.netinheritancestruct

提问by Juliet

I know that structs in .NET do not support inheritance, but its not exactly clear whythey are limited in this way.

我知道 .NET 中的结构不支持继承,但不清楚为什么它们以这种方式受到限制。

What technical reason prevents structs from inheriting from other structs?

什么技术原因阻止结构从其他结构继承?

采纳答案by jonp

The reason value types can't support inheritance is because of arrays.

值类型不能支持继承的原因是数组。

The problem is that, for performance and GC reasons, arrays of value types are stored "inline". For example, given new FooType[10] {...}, if FooTypeis a reference type, 11 objects will be created on the managed heap (one for the array, and 10 for each type instance). If FooTypeis instead a value type, only one instance will be created on the managed heap -- for the array itself (as each array value will be stored "inline" with the array).

问题在于,出于性能和 GC 的原因,值类型的数组是“内联”存储的。例如,给定new FooType[10] {...}, ifFooType是引用类型,将在托管堆上创建 11 个对象(数组一个,每个类型实例 10 个)。如果FooType是值类型,则只会在托管堆上创建一个实例——对于数组本身(因为每个数组值都将与数组“内联”存储)。

Now, suppose we had inheritance with value types. When combined with the above "inline storage" behavior of arrays, Bad Things happen, as can be seen in C++.

现在,假设我们有值类型的继承。当与数组的上述“内联存储”行为结合时,会发生坏事,正如在 C++ 中所见。

Consider this pseudo-C# code:

考虑这个伪 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);

By normal conversion rules, a Derived[]is convertible to a Base[](for better or worse), so if you s/struct/class/g for the above example, it'll compile and run as expected, with no problems. But if Baseand Derivedare value types, and arrays store values inline, then we have a problem.

按照正常的转换规则,aDerived[]可以转换为 a Base[](无论好坏),所以如果你在上面的例子中使用 s/struct/class/g,它会按预期编译和运行,没有问题。但是如果BaseDerived是值类型,并且数组内联存储值,那么我们就有问题了。

We have a problem because Square()doesn't know anything about Derived, it'll use only pointer arithmetic to access each element of the array, incrementing by a constant amount (sizeof(A)). The assembly would be vaguely like:

我们有一个问题,因为Square()对 一无所知Derived,它将仅使用指针算术来访问数组的每个元素,并以常数 ( sizeof(A))递增。大会会模糊地像:

for (int i = 0; i < values.Length; ++i)
{
    A* value = (A*) (((char*) values) + i * sizeof(A));
    value->A *= 2;
}

(Yes, that's abominable assembly, but the point is that we'll increment through the array at known compile-time constants, without any knowledge that a derived type is being used.)

(是的,这是一个令人讨厌的程序集,但关键是我们将在已知编译时常量的数组中递增,而不知道正在使用派生类型。)

So, if this actually happened, we'd have memory corruption issues. Specifically, within Square(), values[1].A*=2would actuallybe modifying values[0].B!

所以,如果这真的发生了,我们就会遇到内存损坏问题。具体而言,内Square()values[1].A*=2实际上是修改values[0].B

Try to debug THAT!

尝试调试那个

回答by Kenan E. K.

Imagine structs supported inheritance. Then declaring:

想象结构支持继承。然后声明:

BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.

a = b; //?? expand size during assignment?

would mean struct variables don't have fixed size, and that is why we have reference types.

这意味着结构变量没有固定的大小,这就是我们有引用类型的原因。

Even better, consider this:

更好的是,考虑一下:

BaseStruct[] baseArray = new BaseStruct[1000];

baseArray[500] = new InheritedStruct(); //?? morph/resize the array?

回答by Martin Liversage

Structs do not use references (unless they are boxed, but you should try to avoid that) thus polymorphism isn't meaningful since there is no indirection via a reference pointer. Objects normally live on the heap and are referenced via reference pointers, but structs are allocated on the stack (unless they are boxed) or are allocated "inside" the memory occupied by a reference type on the heap.

结构不使用引用(除非它们被装箱,但你应该尽量避免这种情况)因此多态性没有意义,因为没有通过引用指针的间接性。对象通常存在于堆上并通过引用指针进行引用,但结构在堆栈上分配(除非它们被装箱)或在堆上的引用类型占用的内存“内部”分配。

回答by Blixt

Here's what the docssay:

这是文档所说的:

Structs are particularly useful for small data structures that have value semantics. Complex numbers, points in a coordinate system, or key-value pairs in a dictionary are all good examples of structs. Key to these data structures is that they have few data members, that they do not require use of inheritance or referential identity, and that they can be conveniently implemented using value semantics where assignment copies the value instead of the reference.

结构对于具有值语义的小型数据结构特别有用。复数、坐标系中的点或字典中的键值对都是结构的好例子。这些数据结构的关键在于它们的数据成员很少,它们不需要使用继承或引用标识,并且它们可以使用值语义方便地实现,其中赋值复制值而不是引用。

Basically, they're supposed to hold simple data and therefore do not have "extra features" such as inheritance. It would probably be technically possible for them to support some limited kind of inheritance (not polymorphism, due to them being on the stack), but I believe it is also a design choice to not support inheritance (as many other things in the .NET languages are.)

基本上,它们应该保存简单的数据,因此没有“额外的功能”,例如继承。它们在技术上可能支持某种有限的继承(不是多态性,因为它们在堆栈上),但我相信不支持继承也是一种设计选择(就像 .NET 中的许多其他东西一样)语言是。)

On the other hand, I agree with the benefits of inheritance, and I think we all have hit the point where we want our structto inherit from another, and realize that it's not possible. But at that point, the data structure is probably so advanced that it should be a class anyway.

另一方面,我同意继承的好处,我认为我们都已经达到了我们希望struct从另一个人那里继承的地步,并意识到这是不可能的。但在这一点上,数据结构可能非常先进,无论如何它都应该是一个类。

回答by Dykam

Class like inheritance is not possible, as a struct is laid directly on the stack. An inheriting struct would be bigger then it parent, but the JIT doesn't know so, and tries to put too much on too less space. Sounds a little unclear, let's write a example:

类继承是不可能的,因为结构直接放在堆栈上。继承结构会比它的父结构大,但 JIT 不知道这一点,并试图在太少的空间上放置太多。听起来有点不清楚,我们来写一个例子:

struct A {
    int property;
} // sizeof A == sizeof int

struct B : A {
    int childproperty;
} // sizeof B == sizeof int * 2

If this would be possible, it would crash on the following snippet:

如果这是可能的,它将在以下代码段上崩溃:

void DoSomething(A arg){};

...

B b;
DoSomething(b);

Space is allocated for the sizeof A, not for the sizeof B.

空间分配给 A 的大小,而不是 B 的大小。

回答by Rui Craveiro

There is a point I would like to correct. Even though the reason structs cannot be inherited is because they live on the stack is the right one, it is at the same a half correct explanation. Structs, like any other value type canlive in the stack. Because it will depend on where the variable is declared they will either live in the stackor in the heap. This will be when they are local variables or instance fields respectively.

有一点我想纠正。即使结构不能被继承的原因是因为它们存在于堆栈中是正确的,但它同时也是正确的解释。结构与任何其他值类型一样可以存在于堆栈中。因为这取决于变量的声明位置,它们将位于堆栈中堆中。这将是当它们分别是局部变量或实例字段时。

In saying that, Cecil Has a Name nailed it correctly.

说到这里,塞西尔有一个名字是正确的。

I would like to emphasize this, value types canlive on the stack. This doesn't mean they always do so. Local variables, including method parameters, will. All others will not. Nevertheless, it still remains the reason they can't be inherited. :-)

我想强调这一点,值类型可以存在于堆栈中。这并不意味着他们总是这样做。局部变量,包括方法参数,都会。所有其他人都不会。尽管如此,这仍然是它们不能被继承的原因。:-)

回答by user38001

Structs are allocated on the stack. This means the value semantics are pretty much free, and accessing struct members is very cheap. This doesn't prevent polymorphism.

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

You could have each struct start with a pointer to its virtual function table. This would be a performance issue (every struct would be at least the size of a pointer), but it's doable. This would allow virtual functions.

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

What about adding fields?

添加字段怎么样?

Well, when you allocate a struct on the stack, you allocate a certain amount of space. The required space is determined at compile time (whether ahead of time or when JITting). If you add fields and then assign to a base type:

好吧,当您在堆栈上分配结构时,您分配了一定数量的空间。所需的空间是在编译时确定的(无论是提前还是在 JITting 时)。如果添加字段然后分配给基本类型:

struct A
{
    public int Integer1;
}

struct B : A
{
    public int Integer2;
}

A a = new B();

This will overwrite some unknown part of the stack.

这将覆盖堆栈的某些未知部分。

The alternative is for the runtime to prevent this by only writing sizeof(A) bytes to any A variable.

另一种方法是让运行时通过仅将 sizeof(A) 字节写入任何 A 变量来防止这种情况发生。

What happens if B overrides a method in A and references its Integer2 field? Either the runtime throws a MemberAccessException, or the method instead accesses some random data on the stack. Neither of these is permissible.

如果 B 覆盖 A 中的方法并引用其 Integer2 字段会发生什么?运行时抛出 MemberAccessException 异常,或者该方法访问堆栈上的一些随机数据。这些都是不允许的。

It's perfectly safe to have struct inheritance, so long as you don't use structs polymorphically, or so long as you don't add fields when inheriting. But these aren't terribly useful.

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

回答by Cecil Has a Name

This seems like a very frequent question. I feel like adding that value types are stored "in place" where you declare the variable; apart from implementation details, this means that there is noobject header that says something about the object, onlythe variable knows what kind of data resides there.

这似乎是一个非常常见的问题。我想添加值类型存储在您声明变量的“位置”;除了实现细节之外,这意味着没有对象头来说明对象的某些内容,只有变量知道那里驻留的数据类型。

回答by Wyatt Barnett

Structs do support interfaces, so you can do some polymorphic things that way.

结构确实支持接口,所以你可以用这种方式做一些多态的事情。

回答by Matt Howells

IL is a stack-based language, so calling a method with an argument goes something like this:

IL 是一种基于堆栈的语言,因此调用带有参数的方法是这样的:

  1. Push the argument onto the stack
  2. Call the method.
  1. 将参数压入堆栈
  2. 调用方法。

When the method runs, it pops some bytes off the stack to get its argument. It knows exactlyhow many bytes to pop off because the argument is either a reference type pointer (always 4 bytes on 32-bit) or it is a value type for which the size is always known exactly.

当该方法运行时,它会从堆栈中弹出一些字节以获取其参数。它确切知道要弹出多少字节,因为参数要么是一个引用类型指针(32 位上总是 4 个字节),要么是一个大小总是准确知道的值类型。

If it is a reference type pointer then the method looks up the object in the heap and gets its type handle, which points to a method table which handles that particular method for that exact type. If it is a value type, then no lookup to a method table is necessary because value types do not support inheritance, so there is only one possible method/type combination.

如果它是引用类型指针,则该方法在堆中查找对象并获取其类型句柄,该句柄指向处理该特定类型的特定方法的方法表。如果是值类型,则不需要查找方法表,因为值类型不支持继承,因此只有一种可能的方法/类型组合。

If value types supported inheritance then there would be extra overhead in that the particular type of the struct would have to placed on the stack as well as its value, which would mean some sort of method table lookup for the particular concrete instance of the type. This would eliminate the speed and efficiency advantages of value types.

如果值类型支持继承,那么将会有额外的开销,因为结构的特定类型及其值必须放在堆栈上,这意味着要为该类型的特定具体实例进行某种方法表查找。这将消除值类型的速度和效率优势。