无法从C#结构继承。对我来说,这是不明白的原因:
我想知道这是CLR中的技术限制,还是C#编译器阻止你做的事情?
编辑:值类型不能有虚拟方法,我意识到这个限制排除了大多数你想要使用继承的场景。但是,这仍然会留下继承 - 聚合。想象一个带有Shape
字段的Colour
结构:我可以编写接受从Shape
派生的任何结构的代码,并访问其Colour
字段,即使我永远不会写一个虚拟Shape.Draw
方法。
我可以想到一个会被非密封值类型打破的场景。值类型应该正确实现Equals
和GetHashCode
;即使System.Object
上的这两个方法都是虚拟的,它们也会在值类型上被非虚拟地调用。即使没有密封值类型,编写从另一个派生的结构的人也无法编写自己的这两种方法的实现,并期望正确调用它们。
我应该指出,我并不是说我应该能够从我自己的代码中继承结构。但是,我想要做的是猜测为什么.NET禁止这种特殊的代码气味。
编辑2:我刚发现this very similar question,其答案实际上是“因为那时价值类型的数组不起作用”。
答案 0 :(得分:52)
原因是大多数继承技术与运行时多态(虚函数)有关,而那些对值类型无效:对于运行时多态有任何意义,对象需要被视为引用 - 这不是特定的.NET也是,它只是虚拟功能如何实现的技术细节。
值类型构成了.NET规则的一个例外,正是为了允许轻量级对象不需要通过引用进行间接寻址。因此,运行时多态性对它们不起作用,并且继承的大多数方面都变得毫无意义。
(有一个例外:可以装箱值类型对象,允许调用从System.Object
继承的虚方法。)
解决您的一个观点:
- 您可以从派生结构转换为基础,因为它们会重叠相同的内存。
不,这是不可能的 - 投射值类型会复制其值。我们这里没有处理引用,因此内存中没有重叠。因此,将值类型转换为其基本类型是没有意义的(再次,除非我们讨论转换为实际执行装箱的object
,并且也在上运行复制的值。)
还不清楚?让我们看一个例子。
假设我们已经得到了假设的struct Shape
,并继承了struct Circle
。 Shape
定义了一个虚拟Draw
方法(接受Graphics
个对象)。现在,假设我们想在画布上绘制一个形状。当然,这非常有效:
var circle = new Circle(new Point(10, 10), 20);
circle.Draw(e.Graphics); // e.Graphics = graphics object of our form.
- 但在这里我们根本没有使用继承。要使用继承,请设想以下DrawObject
辅助方法:
void DrawObject(Shape shape, Graphics g) {
// Do some preparation on g.
shape.Draw(g);
}
我们在其他地方使用Circle
:
var circle = new Circle(new Point(10, 10), 20);
DrawObject(circle, e.Graphics);
- 并且, ka-blam - 此代码不会画一个圆圈。为什么?因为当我们将圆圈传递给DrawObject
方法时,我们会做两件事:
shape
对象实际上不再是Circle
- 既不是原始的也不是副本。相反,它的Circle
部分在复制过程中被“切掉”,只剩下Shape
部分。 shape.Draw
现在调用Draw
的{{1}}方法,而不是Shape
。在C ++中,您实际上可能会导致此行为。因此,C ++中的OOP只能用于指针和引用,而不能直接用于值类型。出于同样的原因,.NET只允许继承引用类型,因为无论如何都不能将它用于值类型。
请注意,如果Circle
是一个接口,上面的代码 在.NET中工作。换句话说,引用类型。现在的情况有所不同:您的Shape
对象将仍然被复制,但它也会被装入参考。
现在,.NET 可以理论上允许您从circle
继承struct
。然后上面的代码就像class
是一个接口一样有用。但是,首先使用Shape
的全部优势消失了:对于所有意图和目的(除了从未传递给另一个方法的局部变量,因此没有继承的实用程序) )您的struct
将表现为不可变的引用类型而不是值类型。
答案 1 :(得分:8)
来自ECMA 335: 值类型将被密封以避免处理值切片的复杂性。 此处指定的限制性规则允许更高效的实施而不会严重 妥协功能。
我不知道'值切片'是什么意思,但我猜它们是密封的,以便有效地实现CLR。
答案 2 :(得分:1)
您可以使用值类型的泛型类型参数进行一些有限的继承形式。
答案 3 :(得分:-1)
因为每个value-type实例都有不同的大小并存储在堆栈中。因此,如果您编写“Base = Derived”,其中“Base”和“Derived”是值类型,您将损坏堆栈。