区分.net中的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。
帮某个兄弟修改一个错误时,再次发现这个错误,于是写了个小例子,让初学者注意。呵呵。
class A { public virtual void Test() { Console.WriteLine("A"); } } class A1 : A { public override void Test() { Console.WriteLine("A1"); } } class A2 : A { public new void Test() { Console.WriteLine("A2"); } } class Test { static void Main(string[] args) { A a1 = new A1(); A a2 = new A2(); a1.Test(); a2.Test(); (a2 as A2).Test(); Console.Read(); } }
输出结果是:
A...
A2...