最近看書的時候看到了虛方法調用這一塊,所以溫習一下這塊的知識,和大家分享一下。
調用虛方法時,具體調用的哪個方法不是在編譯時定的,而是在運行時根據對象的真實類型而定的,因此,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類型表),按前面得到的索引到方法表中找到應該調用的方法。
總結一句話:按同樣的索引查不同的表。