如果C#开放了值类型的继承,会有什么问题发生?

C#里的值类型,都是不可互相继承的,如果开放了这个继承,会发生什么问题?
关注者
80
被浏览
1540
C#的设计让值类型不能多态,只有引用类型可以多态。
不能多态的话要继承来也没啥用。

如果要添加新的方法的话,C#提供了扩展方法,可以使用在struct上。虽说目前C#所允许的在struct上的扩展方法只能接受按值传入的“this”参数,而不像一般struct方法接受按引用传入的“this”参数。
有人提出了改进提案希望C#也支持this ref参数的扩展方法——毕竟VB.NET支持这个。请参考 Allow by-ref Extension methods. · Issue #165 · dotnet/roslyn · GitHub

如果要“继承”相同的字段布局(field layout)的话,那只要嵌入就好了。例如说:
struct CommonHeader {
  int _tag;
  int _id;
}

struct Foo {
  CommonHeader header; // { int _tag; int _id; }
  int _payload;
}
这也不需要继承。事实上为了这种目的使用继承是最错误的用法之一。
可以想像有人会想在这里使用继承,是为了能在Foo里访问tag和id字段时,不需要写header.tag / header.id,而可以直接写tag / id。这完全可以做成一个语法糖,就像Go一样,而不需要继承。

=======================================

而这么设计当然是有原因的。可惜目前在GitHub上公开的C# Design Notes并没有关于那么老的设计的部分,咱就只能猜了。

值类型最重要的特征是:
  • 它的内容不需要通过任何间接就可以直接访问到;引用类型则正好相对,其内容无法直接访问到,而必须通过引用去间接访问到。
  • 按值传递值类型的时候,它里面的所有内容都会被拷贝。
就这样。千万注意这跟什么栈啊堆啊啥乱七八糟的根本没关系。

那我们要是允许它多态的话会怎样呢?拿C++来举个最简单的例子
struct CommonHeader {
  int tag_;
  int id_;
};

struct Foo : public CommonHeader {
  int payload_;
};
看起来很正常?那让我们来写个函数看看:
const int MARK = 1;

void mark(CommonHeader obj) { // pass-by-value of a value type
  obj.tag_ = MARK;
}

int main() {
  Foo foo;
  mark(foo);

  return 0;
}
这里在mark()函数里发生了两件“有趣”的事:
  • pass-by-value:对象传了份拷贝进来,所以对tag_字段的写只反映在了传进来的拷贝上而没有反映在原本的Foo对象上。
  • object slicing:原本要传的对象是Foo类型的,但实际传入mark()函数的拷贝却只包含了CommonHeader类型的内容——切掉了Foo里额外的内容。来看Wiki的解说:Object slicing,或者 Object Slicing in C++

所以我们想在C#里吃对象切糕不?多半不想吧…

=======================================

最后…

Eric Lippert大大发过很多篇关于C#的值类型的博文:Value Types | Fabulous Adventures In Coding。好好读,再说值类型是为了把值放在栈上的请自己拖出去打pp >_<