2{
3 public Base()
4 {
5 Method1();
6 }
7 public virtual void Method1() {
8 Console.WriteLine("In Base's Method1()");
9 }
10}
11public class Derived: Base
12{
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?
原因:在执行Derived的构造函数之前,要先执行父类Base的构造函数,而在Base的构造函数中,调用了虚方法Method1,此时CLR会监测到this的运行时类型为Derived,因此在调用(this.)Method1时,实际调用的是Derived.Method1()方法;而此时还没有执行父类Derived构造函数,因此value还没有赋值(默认为0),因此要执行语句:Console.WriteLine("value != 42, what is wrong?");
下面详细介绍virtual/new/override 这些关键字的意义
1. virtual:当一个方法为是virtual方法,CLR在调用该方法时,会监测此时this的运行时类型(可以通过this.GetType().ToString()查看),然后调用运行时类型上的重写(override)方法。CLR在调用virtual方法时,产生callvirt指令(IL),该指令在被JIT编译成汇编语言时,产生三条汇编指令(可以在“命令”窗口中输入disasm来查看编译后的IA-32代码):
mov ecx,esi; //将目标对象的引用(在这里是this)存储在IA-32 ecx寄存器中;
mov eax,dword ptr[ecx];//针对虚方法调用的指令,将对象的类型句柄存储在eax寄存器中;
call dword ptr [eax+offset];通过对象的类型句柄和方法在方法表中的偏移量来定位目标方法的实际地址;
而对于非virtual方法,CLR在调用该方法时,产生call指令(IL),该指令在被JIT编译成会被语言时,只产生两条汇编指令(相比callvirt而言,call指令不需在运行时检测对象的运行时类型):
mov ecx,esi;//把目标对象的引用放进ecx寄存器
call methodAddress;//直接调用methodAddress指向的目标方法
2. new:子类在继承了父类后,可以用new来隐藏父类中的方法。此时,在子类的方法表(Mathod Table)中,仍然保留父类中该方法的方法槽(slot,.net对象模型中,类型中的每个方法在方法表中都占用一个方法槽)。因此,我们仍然可以将子类的引用强制转换成父类的引用,来调用父类中的非virtual或virtual方法。例如:下面第四题中的代码,如果我们将override改成new,则最终结果是:执行父类Base的Method1,输出“In Base's Method1()”。
3. override:子类在继承了父类后,可以用override来重写父类中的方法。此时,在子类的方法表中,不再保留父类中该方法的方法槽。当我们将子类的引用强制转换成父类的引用,来试图调用父类中的virtual方法时,实际上是不能执行成功的,因为CLR检测到此时对象的运行时类型是子类类型,于是将调用分派(dispatch)到子类的方法上。例如:下面第四题中的代码,在父类Base中调用子类中已重写的Method1方法,CLR检测到此时this的运行时类型为Derived,因此仍然会调用Derived中重写的Method1。
例如:上面中的例子,可以用VS2005的SOS扩展来分析Base/Derived的对象模型(调试过程中,在“即使窗口”中输入的命令用蓝色显示,其余的为输出信息)(有关SOS的使用,可以参考我以前写的:《使用SOS - 在Visual Studio中启用非托管代码调试来支持本机代码调试 》)。可以看出,在使用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,得到的分析结果如下:
!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()