深入理解virtual/new/override 这些关键字的意义

本文详细解析了C#中virtual、new和override关键字的使用及其对方法调用的影响,通过实例说明了虚方法调用的具体过程,包括CLR如何在运行时确定调用哪个方法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 1 None.gif public   class  Base
 2 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
 3InBlock.gif    public Base()
 4ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
 5InBlock.gif        Method1();
 6ExpandedSubBlockEnd.gif    }

 7ExpandedSubBlockStart.gifContractedSubBlock.gif    public virtual void Method1() dot.gif{
 8InBlock.gif        Console.WriteLine("In Base's Method1()");
 9ExpandedSubBlockEnd.gif    }

10ExpandedBlockEnd.gif}

11 None.gif public   class  Derived: Base
12 ExpandedBlockStart.gifContractedBlock.gif dot.gif {
13InBlock.gif    private int value;
14InBlock.gif    public Derived()
15ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
16InBlock.gif        value = 42;
17ExpandedSubBlockEnd.gif    }

18InBlock.gif
19InBlock.gif    public override void Method1()
20ExpandedSubBlockStart.gifContractedSubBlock.gif    dot.gif{
21InBlock.gif        if (value == 42) Console.WriteLine("value == 42, all is good");
22InBlock.gif        else Console.WriteLine("value != 42, what is wrong?");
23ExpandedSubBlockEnd.gif    }

24ExpandedBlockEnd.gif}

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
 Address       MT     Size
013f8828 00a531f0       12    
total 1 objects
Statistics:
      MT    Count    TotalSize Class Name
00a531f0        1           12 Bingosoft.Training2007.CSharp.Derived
Total 1 objects 

!DumpMT -MD 00a531f0
EEClass: 00a513f8
Module: 00a52c14
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()
793539c0   7913bd50   PreJIT System.Object.Equals(System.Object)
793539b0   7913bd68   PreJIT System.Object.GetHashCode()
7934a4c0   7913bd70   PreJIT System.Object.Finalize()
00a53248   00a531e8      JIT Bingosoft.Training2007.CSharp.Derived.Method1()
00a53238   00a531e0      JIT Bingosoft.Training2007.CSharp.Derived..ctor()

 

2. 如果我们将Derived类中的override改成new,得到的分析结果如下:

!dumpheap -type Derived
PDB symbol for mscorwks.dll not loaded
 Address       MT     Size
013f8828 00a531f0       12    
total 1 objects
Statistics:
      MT    Count    TotalSize Class Name
00a531f0        1           12 Bingosoft.Training2007.CSharp.Derived
Total 1 objects

!DumpMT -MD 00a531f0
EEClass: 00a513f8
Module: 00a52c14
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()
793539c0   7913bd50   PreJIT System.Object.Equals(System.Object)
793539b0   7913bd68   PreJIT System.Object.GetHashCode()
7934a4c0   7913bd70   PreJIT System.Object.Finalize()
00a531b0   00a53150      JIT Bingosoft.Training2007.CSharp.Base.Method1()
00a53240   00a531e0      JIT Bingosoft.Training2007.CSharp.Derived..ctor()
00a53250   00a531e8     NONE Bingosoft.Training2007.CSharp.Derived.Method1()

 

转载于:https://www.cnblogs.com/happyhippy/archive/2007/07/22/827583.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值