public class Base2
{3
public Base()4
{5
Method1();6
}7
public virtual void Method1() {8
Console.WriteLine("In Base's Method1()");9
}10
}11
public class Derived: Base12
{13
private int value;14
public Derived()15
{16
value = 42;17
}18

19
public override void Method1()20
{21
if (value == 42) Console.WriteLine("value == 42, all is good");22
else Console.WriteLine("value != 42, what is wrong?");23
}24
}Derived dev = new Derived();
問:屏幕的輸出是多少?
輸出:value != 42, what is wrong?
原因:在執(zhí)行Derived的構(gòu)造函數(shù)之前,要先執(zhí)行父類Base的構(gòu)造函數(shù),而在Base的構(gòu)造函數(shù)中,調(diào)用了虛方法Method1,此時CLR會監(jiān)測到this的運行時類型為Derived,因此在調(diào)用(this.)Method1時,實際調(diào)用的是Derived.Method1()方法;而此時還沒有執(zhí)行父類Derived構(gòu)造函數(shù),因此value還沒有賦值(默認為0),因此要執(zhí)行語句:Console.WriteLine("value != 42, what is wrong?");
下面詳細介紹virtual/new/override 這些關(guān)鍵字的意義
1. virtual:當一個方法為是virtual方法,CLR在調(diào)用該方法時,會監(jiān)測此時this的運行時類型(可以通過this.GetType().ToString()查看),然后調(diào)用運行時類型上的重寫(override)方法。CLR在調(diào)用virtual方法時,產(chǎn)生callvirt指令(IL),該指令在被JIT編譯成匯編語言時,產(chǎn)生三條匯編指令(可以在“命令”窗口中輸入disasm來查看編譯后的IA-32代碼):
mov ecx,esi; //將目標對象的引用(在這里是this)存儲在IA-32 ecx寄存器中;
mov eax,dword ptr[ecx];//針對虛方法調(diào)用的指令,將對象的類型句柄存儲在eax寄存器中;
call dword ptr [eax+offset];通過對象的類型句柄和方法在方法表中的偏移量來定位目標方法的實際地址;
而對于非virtual方法,CLR在調(diào)用該方法時,產(chǎn)生call指令(IL),該指令在被JIT編譯成會被語言時,只產(chǎn)生兩條匯編指令(相比callvirt而言,call指令不需在運行時檢測對象的運行時類型):
mov ecx,esi;//把目標對象的引用放進ecx寄存器
call methodAddress;//直接調(diào)用methodAddress指向的目標方法
2. new:子類在繼承了父類后,可以用new來隱藏父類中的方法。此時,在子類的方法表(Mathod Table)中,仍然保留父類中該方法的方法槽(slot,.net對象模型中,類型中的每個方法在方法表中都占用一個方法槽)。因此,我們?nèi)匀豢梢詫⒆宇惖囊脧娭妻D(zhuǎn)換成父類的引用,來調(diào)用父類中的非virtual或virtual方法。例如:下面第四題中的代碼,如果我們將override改成new,則最終結(jié)果是:執(zhí)行父類Base的Method1,輸出“In Base's Method1()”。
3. override:子類在繼承了父類后,可以用override來重寫父類中的方法。此時,在子類的方法表中,不再保留父類中該方法的方法槽。當我們將子類的引用強制轉(zhuǎn)換成父類的引用,來試圖調(diào)用父類中的virtual方法時,實際上是不能執(zhí)行成功的,因為CLR檢測到此時對象的運行時類型是子類類型,于是將調(diào)用分派(dispatch)到子類的方法上。例如:下面第四題中的代碼,在父類Base中調(diào)用子類中已重寫的Method1方法,CLR檢測到此時this的運行時類型為Derived,因此仍然會調(diào)用Derived中重寫的Method1。
例如:上面中的例子,可以用VS2005的SOS擴展來分析Base/Derived的對象模型(調(diào)試過程中,在“即使窗口”中輸入的命令用藍色顯示,其余的為輸出信息)(有關(guān)SOS的使用,可以參考我以前寫的:《使用SOS - 在Visual Studio中啟用非托管代碼調(diào)試來支持本機代碼調(diào)試 》)。可以看出,在使用override的情況下,子類的方法表中只有一個Method1方法槽(下面以紅色顯示的部分),子類Derived覆蓋重寫了父類Base的Method1方法;而在使用new的情況下,子類的方法表中有兩個Method1方法槽,子類Derived僅僅只是對外隱藏了父類Base中的Method1。
1. override:
!dumpheap -type Derived
PDB symbol for mscorwks.dll not loaded
total 1 objects
Statistics:
MT Count TotalSize Class Name
Total 1 objects
!DumpMT -MD
EEClass:
Module:
Name: Bingosoft.Training2007.CSharp.Derived
mdToken: 02000004 (H:\Training\DotNetExam\DotNetExam\bin\Debug\DotNetExam.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 6
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
79354bec 7913bd48 PreJIT System.Object.ToString()
793539b0 7913bd68 PreJIT System.Object.GetHashCode()
2. 如果我們將Derived類中的override改成new,得到的分析結(jié)果如下:
!dumpheap -type Derived
PDB symbol for mscorwks.dll not loaded
total 1 objects
Statistics:
MT Count TotalSize Class Name
Total 1 objects
!DumpMT -MD
EEClass:
Module:
Name: Bingosoft.Training2007.CSharp.Derived
mdToken: 02000004 (H:\Training\DotNetExam\DotNetExam\bin\Debug\DotNetExam.exe)
BaseSize: 0xc
ComponentSize: 0x0
Number of IFaces in IFaceMap: 0
Slots in VTable: 7
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
79354bec 7913bd48 PreJIT System.Object.ToString()
793539b0 7913bd68 PreJIT System.Object.GetHashCode()


浙公網(wǎng)安備 33010602011771號