为何C#编译器会调用未显式引用类的最具体重写实现?
为什么
A a = new C(); a.helloWorld();会调用B的实现? 要搞懂这个行为,得把virtual/override/new的本质和C#的虚方法机制说透:
核心规则梳理
virtual:在基类中标记方法为可被重写,同时为该方法建立虚方法表(vtable),多态调用时会从这里找实际的实现。override:在派生类中延续虚方法的多态链——它会替换虚方法表中对应位置的实现,所有通过基类引用的多态调用都会指向这个新实现。new:这不是重写,是隐藏基类的同名成员——它会创建一个全新的、和基类虚方法无关的方法,这个方法不会进入虚方法表,只有当引用类型是当前派生类(或向下转型到该类)时,才会被调用。
对应你的继承链分析
- 基类
A的helloWorld是virtual,所以它的虚方法表里有一个指向A.helloWorld的条目。 B用override重写后,B的虚方法表里,helloWorld的条目被替换成B.helloWorld——这意味着所有通过A或B引用指向B实例的调用,都会走B的实现。C用new声明helloWorld时,并没有修改虚方法表里的条目——C的虚方法表中,helloWorld依然继承自B的override版本。new只是在C类的"非多态调用路径"上,把同名方法替换成了自己的实现。
解释你的代码行为
当你写A a = new C(); a.helloWorld();时:
- 变量
a是A类型的引用,调用helloWorld会触发多态调用,也就是去查实例(C对象)的虚方法表。 - 而
C的虚方法表里,helloWorld的条目还是B.helloWorld(因为C没override这个方法,只是用new隐藏了自己的同名方法),所以最终调用的是B的实现。
如果换成C c = new C(); c.helloWorld();,因为引用类型是C,会直接调用C用new声明的方法;如果写成((B)c).helloWorld();,又会回到虚方法表调用B的实现。
C#文档的相关定义
C#语言规范明确说明:使用new关键字声明的成员,是对基类同名成员的隐藏,而非覆盖。通过基类引用访问该成员时,会使用基类的实现;只有通过派生类引用访问时,才会使用new声明的成员。这就是你看到这个行为的官方依据。
内容的提问来源于stack exchange,提问作者Mr Guliarte
相关产品推荐
相关产品推荐

