zoukankan      html  css  js  c++  java
  • .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在运行时的过程
    1. 字段分配
      对象创建首先为其父类字段分配空间,所以Chicken会找其父类Bird字段,Bird找其父类Animal一次递归到Object。所以对象字段在托管堆栈中的存储顺序是由上至下(先父类字段在子类字段),这样如果子类中出现同名字段,编译器自动认为这是两个不同的字段。
    2. 方法表的创建
      方法表的创建是类第一次加载到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.哪这个运行的为那个类型的方法表啊?

    解答问题

    1. 通过IL语言分析newoverride
      通过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()的方法,因BirdChicken都新加了各自的Eat()方法。
    再次论证(直接修改IL代码,调用Chicken类型方法表中的Chicken::Eat()的方法):

    1. 通过Developer Command Prompt工具反编译程序集ildasm 你的程序集.exe /out:test.il
    2. 打开test.il文件将IL_000f: callvirt instance void ConsoleApp.Inheritance.InheritDemo.Bird::Eat()修改为IL_000f: callvirt instance void ConsoleApp.Inheritance.InheritDemo.Chicken::Eat()
    3. 再编译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.
  • 相关阅读:
    RQNOJ 34 紧急援救
    Codevs 2080 特殊的质数肋骨
    POJ2975 Nim
    Bzoj1016 最小生成树计数
    POJ3613 Cow Relays
    POJ1386 Play on Words
    [从hzwer神犇那翻到的模拟赛题] 合唱队形
    HDU2824 The Euler function
    HDU1576 A/B
    HDU2669 Romantic
  • 原文地址:https://www.cnblogs.com/LoveTomato/p/8489924.html
Copyright © 2011-2022 走看看