.net 方法的调用
本节主要讲.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.



 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号