最近看书的时候看到了虚方法调用这一块,所以温习一下这块的知识,和大家分享一下。

调用虚方法时,具体调用的哪个方法不是在编译时定的,而是在运行时根据对象的真实类型而定的,因此,CLR对于虚方法调用采用了动态分派的方法

      举两个例子,定义两个继承关系的类Parent和Child

 1 class Parent
2 {
3 public virtual void virtualMtd()
4 {
5 Console.WriteLine("Parent");
6 }
7 }
8 class Child : Parent
9 {
10 public override void virtualMtd()
11 {
12 Console.WriteLine("Child");
13 }
14 }

      Parent 类中定义了一个虚方法virtualMtd(),Child类中重写了此方法。

      Main函数的代码如下

 1 static void Main(string[] args)
2 {
3
4 Parent p = new Parent();
5 p.virtualMtd();
6 Child c = new Child();
7 c.virtualMtd();
8 p = c;
9 p.virtualMtd();
10 }

     在main方法中一共有3处调用了virtualMtd()。编译一下。

     在使用ildasm查看程序入口Mian方法的IL代码如下:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       37 (0x25)
  .maxstack  1
  .locals init ([0] class ConsoleApplication2.Parent p,
           [1] class ConsoleApplication2.Child c)
  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication2.Parent::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance void ConsoleApplication2.Parent::virtualMtd()
  IL_000d:  nop
  IL_000e:  newobj     instance void ConsoleApplication2.Child::.ctor()
  IL_0013:  stloc.1
  IL_0014:  ldloc.1
  IL_0015:  callvirt   instance void ConsoleApplication2.Parent::virtualMtd()
  IL_001a:  nop
  IL_001b:  ldloc.1
  IL_001c:  stloc.0
  IL_001d:  ldloc.0
  IL_001e:  callvirt   instance void ConsoleApplication2.Parent::virtualMtd()
  IL_0023:  nop
  IL_0024:  ret
} // end of method Program::Main

 

    请注意加颜色的三行分别对应下边的2,4,6

1  Parent p = new Parent();
2 p.virtualMtd();
3 Child c = new Child();
4 c.virtualMtd();
5 p = c;
6 p.virtualMtd();

 

      可以看到在C#的源代码中的三句话不一样,但生成的IL语句都是一样的。

      通过向堆栈中压入不同对象引用,3条一样callvirt指令将调用所引用对象的同名方法,这正是虚方法的调用实质。

其实在Parent类类型方法表里新添加一个行:虚方法virtualMtd()在子类类型方法表中也有一个virtualMtd(),并且他们在各自的类型方法表里的保存的位置也是一样的。

IL_0015:  callvirt   instance void ConsoleApplication2.Parent::virtualMtd()

      CLR在执行IL指令时,会根据Parent类型表中virtualMtd方法的位置得到一个索引号,然后,根据执行此方法的对象的真实类型查对应的类型表(当前对象为Child类型,则查Child类型表),按前面得到的索引到方法表中找到应该调用的方法。

总结一句话:按同样的索引查不同的表。

posted on 2012-01-11 18:21  Gordon_天下  阅读(1645)  评论(10编辑  收藏  举报