本节主要讲.net实体方法在继承下的调用。参考《你必须知道的.Net》又参考了很多博客,加上自己的理解,因讨论比较深如有不当之处,还望指正。
实例代码
public abstract class Animal
{
public abstract void ShowType();
public virtual void Eat()
{
Console.WriteLine("Animal always eat.");
}
}
public class Bird : Animal
{
private string type = "Bird";
public override void ShowType()
{
Console.WriteLine("Type is {0}", type);
}
public new void Eat()
{
Console.WriteLine("Bird can eat.");
}
}
public class Chicken : Bird
{
private string type = "Chicken";
public override void ShowType()
{
Console.WriteLine("Type is {0}", type);
}
public new void Eat()
{
Console.WriteLine("Chicken can eat.");
}
}
代码调用一:
static void Main(string[] args)
{
Bird bird = new Chicken();
bird.ShowType();
bird.Eat();
Console.ReadKey();
}
大家猜下运行结果,结果如下:

理论基础
- CLR层次上理解继承
Bird bird = new Bird(); Chicken chicken = new Chicken();
Bird bird创建的是一个Bird类型的引用,而new Bird()完成的是创建Bird对象(在托管堆栈上分配内存空间)。我们以Chicken对象的创建为例,分析CLR在运行时的过程
- 字段分配
对象创建首先为其父类字段分配空间,所以Chicken会找其父类Bird字段,Bird找其父类Animal一次递归到Object。所以对象字段在托管堆栈中的存储顺序是由上至下(先父类字段在子类字段),这样如果子类中出现同名字段,编译器自动认为这是两个不同的字段。 - 方法表的创建
方法表的创建是类第一次加载到AppDomain时完成(在对象创建之前)。在对象创建时将其TypeHandle指向方法表在Loader Heap上的地址。方法表的创建也是由上至下(先创建父类方法,在创建子类方法),这样子类方法与父类方法重名编译器自动认为是两个不同的方法,还有一点override重写虚方法(或抽象方法)时,将在已分配的父类方法上重写,不在添加新的方法。

- 引用类型与对象类型
Bird bird; bird = new Chicken();
Bird bird;声明引用类型为Bird的变量,分配在栈上(占4个字节)
bird = new Chicken();为创建类型为Chicken的对象,并堆栈地址赋值给bird变量。
所以bird的引用类型为Bird,而对象类型(变量指向的实体)为Chicken类型对象。
问题讨论
回过来看 实例代码 ,不知道大家是否猜中了上面的运行结果。
那么运行时bird到底时调用哪个类型上的方法表啊,是 Bird类型,还是Chicken类型?bird.ShowType() 运行结果Type is Chicken,可知运行的为Chicken类型的方法表。但是bird.Eat(); 运行结果为Bird can eat.哪这个运行的为那个类型的方法表啊?
解答问题
- 通过IL语言分析
new和override
通过ILSpy工具查看程序集中间代码。Bird类型下的ShowType()和Eat()方法
.method public hidebysig virtual
instance void ShowType () cil managed
{
// Method begins at RVA 0x208f
// Code size 19 (0x13)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Type is {0}"
IL_0006: ldarg.0
IL_0007: ldfld string ConsoleApp.Inheritance.InheritDemo.Bird::'type'
IL_000c: call void [mscorlib]System.Console::WriteLine(string, object)
IL_0011: nop
IL_0012: ret
} // end of method Bird::ShowType
.method public hidebysig
instance void Eat () cil managed
{
// Method begins at RVA 0x20a3
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Bird can eat."
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Bird::Eat
关键字virtual表明重写ShowType()方法。其实Eat()方法是为Bird类型新建了一个Eat()方法(你可以在Bird类下添加新方法Eat2()指令将同样)则Bird与Chicken的方法表如下

2.方法调用的IL代码
bird.ShowType();
反编译为:
IL_0007: ldloc.0
IL_0008: callvirt instance void ConsoleApp.Inheritance.InheritDemo.Animal::ShowType()
IL_000d: nop
callvirt调用被Chicken重写ShowType()方法。
bird.Eat();
反编译为:
IL_000e: ldloc.0
IL_000f: callvirt instance void ConsoleApp.Inheritance.InheritDemo.Bird::Eat()
IL_0014: nop
这里先假设是调用的是Chicken类型方法表中的Bird::Eat()的方法,因Bird与Chicken都新加了各自的Eat()方法。
再次论证(直接修改IL代码,调用Chicken类型方法表中的Chicken::Eat()的方法):
- 通过Developer Command Prompt工具反编译程序集
ildasm 你的程序集.exe /out:test.il - 打开test.il文件将
IL_000f: callvirt instance void ConsoleApp.Inheritance.InheritDemo.Bird::Eat()修改为IL_000f: callvirt instance void ConsoleApp.Inheritance.InheritDemo.Chicken::Eat() - 再编译
ilasm test.il /res:test.res /out:test.exe
运行test.exe结果为:

总结
- 编译时,依照引用类型。所以
bird.Eat();编译为:Bird::Eat()。 - 运行时调用的对象实体类型的方法。也可通过反射调用调用对象类型上的方法
bird.GetType().GetMethod("Eat").Invoke(bird, null);输出为Chicken can eat.