call vs callvirt; virtual, override, new

看到一些C#书籍,介绍virtual, override, new,我觉得还不够深入。今天有点时间来探讨一下内部的机制,如果有什么不准确的地方,请不吝赐教。

我觉得C#编译器编译为IL语言时,遵循下面一个规律
         * 
         * 对于非虚方法编译为IL时候,
         *编译为,找到离编译时所能知道的对象类型最近的并且定义过这个函数的class,定义为 <离编译时所能知道的对象类型最近的并且定义过这个函数的class>.<Function>
         *如果使用this调用,使用call; 如果使用instance调用,使用callvirt (因为instance可能会抛nullreferenceexception,而this永远不会)
         * 运行时,如果是call,直接调用这个<找到离编译时所能知道的对象类型最近的并且定义过这个函数的class>.<Function>
         * 但如果是callvirt,会到虚函数表中查找(其实多此一举),什么也不会有,然后仍然调用<找到离编译时所能知道的对象类型最近的并且定义过这个函数的class>.<Function>
         *
         * 对虚方法编译为IL时候,
         *编译为,找到离编译时所能知道的对象类型最远的并且定义过这个函数的class,定义为 <离编译时所能知道的对象类型最远的并且定义过这个函数的class>.<Function>
         * 一般使用callvirt,除非base.虚方法(), 值类型.虚方法(), Sealed Instance.虚方法()
         *然后,在运行时,知道对象的实际类型,再根据虚函数表,调用离实际类型最近的,并且override这个函数的class,然后调用这个override的Function 
         *对于上述几点,注意关键字new的阻断作用


snippet1:
 1 None.gif   class  Summary
 2 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
 3InBlock.gif        static void Main(string[] args)
 4ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 5InBlock.gif           
 6InBlock.gif            Base b = new DerivedLayer1();
 7InBlock.gif            b.DoNoVirtualWork();
 8InBlock.gif            b.DoVirtualWork();
 9InBlock.gif  
10InBlock.gif            Console.ReadLine();
11InBlock.gif
12ExpandedSubBlockEnd.gif        }

13ExpandedBlockEnd.gif    }

14 None.gif
15 None.gif     public   class  Base
16 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
17InBlock.gif        public virtual void DoVirtualWork()
18ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
19InBlock.gif            Console.WriteLine("VirtualWork Base 000");
20ExpandedSubBlockEnd.gif        }

21InBlock.gif
22InBlock.gif        public void DoNoVirtualWork()
23ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
24InBlock.gif            Console.WriteLine("Non Virtual Base 000");
25ExpandedSubBlockEnd.gif        }

26ExpandedBlockEnd.gif    }

27 None.gif     public   class  DerivedLayer1 : Base
28 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
29InBlock.gif        public override void DoVirtualWork()
30ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
31InBlock.gif            Console.WriteLine("VirtualWork DerivedLayer1 111");
32ExpandedSubBlockEnd.gif        }

33InBlock.gif
34ExpandedBlockEnd.gif    }


那么结果是什么呢?
result1:
Non Virtual Base 000
VirtualWork DerivedLayer1 111
结果正如我们料想的那样,很简单, emteeth.gif
我们看看Main函数的IL:
None.gif .method  private  hidebysig  static   void   Main( string [] args) cil managed
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif  .entrypoint
InBlock.gif  
// Code size       28 (0x1c)
InBlock.gif
  .maxstack  1
InBlock.gif  .locals init ([
0class ConsoleApplication1.Base b)
InBlock.gif  IL_0000:  nop
InBlock.gif  IL_0001:  newobj     instance 
void ConsoleApplication1.DerivedLayer1::.ctor()
InBlock.gif  IL_0006:  stloc.
0
InBlock.gif  IL_0007:  ldloc.
0
InBlock.gif  IL_0008:  callvirt   instance 
void ConsoleApplication1.Base::DoNoVirtualWork()
InBlock.gif  IL_000d:  nop
InBlock.gif  IL_000e:  ldloc.
0
InBlock.gif  IL_000f:  callvirt   instance 
void ConsoleApplication1.Base::DoVirtualWork()
InBlock.gif  IL_0014:  nop
InBlock.gif  IL_0015:  call       
string [mscorlib]System.Console::ReadLine()
InBlock.gif  IL_001a:  pop
InBlock.gif  IL_001b:  ret
ExpandedBlockEnd.gif}
  //  end of method Summary::Main
None.gif

对于
Base b = new DerivedLayer1();
 b.DoNoVirtualWork();  => callvirt   instance void ConsoleApplication1.Base::DoNoVirtualWork()
//上面这句,因为编译器知道b的类型是Base,而是非虚函数,编译器寻找离Base最近而且定义过DoNoVirtualWork()的class,就是Base本身,  所以要调用ConsoleApplication1.Base::DoNoVirtualWork() 即:<离编译时所能知道的对象类型最近的并且定义过这个函数的class>.<Function>
 b.DoVirtualWork();      =>  callvirt   instance void ConsoleApplication1.Base::DoVirtualWork()
//上面这句,因为编译器知道b的类型是Base,它是一个虚函数,所以要寻找最早定义过DoVirtualWork()的class,就是Base本身,所以是 ConsoleApplication1.Base::DoVirtualWork(),即<离编译时所能知道的对象类型最远的并且定义过这个函数的class>.<Function>

下面代码换成snippet2
 1 None.gif   class  Summary
 2 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
 3InBlock.gif        static void Main(string[] args)
 4ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 5InBlock.gif
 6InBlock.gif            DerivedLayer1 d1 = new DerivedLayer1();
 7InBlock.gif
 8InBlock.gif            d1.DoNoVirtualWork();
 9InBlock.gif            d1.DoVirtualWork();
10InBlock.gif  
11InBlock.gif            Console.ReadLine();
12InBlock.gif
13ExpandedSubBlockEnd.gif        }

14ExpandedBlockEnd.gif    }

15 None.gif
16 None.gif     public   class  Base
17 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
18InBlock.gif        public virtual void DoVirtualWork()
19ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
20InBlock.gif            Console.WriteLine("VirtualWork Base 000");
21ExpandedSubBlockEnd.gif        }

22InBlock.gif
23InBlock.gif        public void DoNoVirtualWork()
24ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
25InBlock.gif            Console.WriteLine("Non Virtual Base 000");
26ExpandedSubBlockEnd.gif        }

27ExpandedBlockEnd.gif    }

28 None.gif     public   class  DerivedLayer1 : Base
29 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
30InBlock.gif
31InBlock.gif        public new void DoNoVirtualWork()
32ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
33InBlock.gif            Console.WriteLine("Non Virtual DerivedLayer1 111");
34ExpandedSubBlockEnd.gif        }
35InBlock.gif
36ExpandedBlockEnd.gif    }

37 None.gif


注意DerivedLayer1没有override Base的DoVirtualWork(), 但是却用new 重新定义了DoNoVirtualWork()
这时候的结果会是什么呢
我们继续使用上面的规律来产生IL
DerivedLayer1 d1 = new DerivedLayer1();
 d1.DoNoVirtualWork();
//对于上面这句语句,编译器所能知道的类型是DerivedLayer1 ,它是非虚拟函数,而离DerivedLayer1 最近,且定义过DoNoVirtualWork()的class就是它自己,所以根据<找到离编译时所能知道的对象类型最近的并且定义过这个函数的class>>.<Function>, 应该是: callvirt DerivedLayer1::DoNoVirtualWork(); 我们用ildasm看一下,果然如此:callvirt   instance void ConsoleApplication1.DerivedLayer1::DoNoVirtualWork(),所以输出“Non Virtual DerivedLayer1 111”;
而d1.DoVirtualWork();
//对于上面这一句,<离编译时所能知道的对象类型最远的并且定义过这个函数的class>.<Function>,编译器这是所能知道d1的类型是DerivedLayer1 ,离DerivedLayer1 最远并且定义过DoVirtualWork()的class是Base,所以IL应该是callvirt Base::DoVirtualWork(),使用ildasm查看
callvirt   instance void ConsoleApplication1.Base::DoVirtualWork() ,也是如此,使用callvirt在运行时,需要在v-table中产看离当前实际类型(DerivedLayer1),最近并且定义过DoVirtualWork的class是Base,所以输出“VirtualWork Base 000”;
所以结果是
Non Virtual DerivedLayer1 111
VirtualWork Base 000

下面复杂一点,需要考虑new的阻断情况,但仍然符合这个规律。

 1 None.gif   class  Summary
 2 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
 3InBlock.gif        static void Main(string[] args)
 4ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
 5InBlock.gif            Base b = new DerivedLayer2();
 6InBlock.gif            b.DoNoVirtualWork();
 7InBlock.gif            b.DoVirtualWork();
 8InBlock.gif
 9InBlock.gif            Console.WriteLine("********");
10InBlock.gif
11InBlock.gif            DerivedLayer1 d1 = new DerivedLayer2();
12InBlock.gif            d1.DoNoVirtualWork();
13InBlock.gif            d1.DoVirtualWork();
14InBlock.gif
15InBlock.gif            Console.ReadLine();
16InBlock.gif
17ExpandedSubBlockEnd.gif        }

18ExpandedBlockEnd.gif    }

19 None.gif
20 None.gif     public   class  Base
21 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
22InBlock.gif        public virtual void DoVirtualWork()
23ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
24InBlock.gif            Console.WriteLine("VirtualWork Base 000");
25ExpandedSubBlockEnd.gif        }

26InBlock.gif
27InBlock.gif        public void DoNoVirtualWork()
28ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
29InBlock.gif            Console.WriteLine("Non Virtual Base 000");
30ExpandedSubBlockEnd.gif        }

31ExpandedBlockEnd.gif    }

32 None.gif
33 None.gif     public   class  DerivedLayer1 : Base
34 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
35InBlock.gif        public new virtual void DoVirtualWork()
36ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
37InBlock.gif            Console.WriteLine("VirtualWork DerivedLayer1 111");
38ExpandedSubBlockEnd.gif        }

39InBlock.gif
40InBlock.gif
41InBlock.gif        public new void DoNoVirtualWork()
42ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
43InBlock.gif            Console.WriteLine("Non Virtual DerivedLayer1 111");
44ExpandedSubBlockEnd.gif        }

45InBlock.gif
46ExpandedBlockEnd.gif    }

47 None.gif
48 None.gif     public   class  DerivedLayer2 : DerivedLayer1
49 ExpandedBlockStart.gifContractedBlock.gif     dot.gif {
50InBlock.gif        public override void DoVirtualWork()
51ExpandedSubBlockStart.gifContractedSubBlock.gif        dot.gif{
52InBlock.gif            Console.WriteLine("VirtualWork DerivedLayer2 222");
53InBlock.gif
54ExpandedSubBlockEnd.gif        }

55InBlock.gif
56ExpandedBlockEnd.gif    }

这次的结果会是什么呢?注意"new",也许不是那么容易
让我们来看一下产生的IL语句

 Base b = new DerivedLayer2();
 b.DoNoVirtualWork();  => callvirt   instance void ConsoleApplication1.Base::DoNoVirtualWork()
b.DoVirtualWork();        => callvirt   instance void ConsoleApplication1.Base::DoVirtualWork()

Console.WriteLine("********");

DerivedLayer1 d1 = new DerivedLayer2(); 
d1.DoNoVirtualWork();=>callvirt   instance void ConsoleApplication1.DerivedLayer1::DoNoVirtualWork()
d1.DoVirtualWork();     =>callvirt   instance void ConsoleApplication1.DerivedLayer1::DoVirtualWork()

结果是:
Non Virtual Base 000
VirtualWork Base 000
********
Non Virtual DerivedLayer1 111
VirtualWork DerivedLayer2 222


另外,什么时候会编译成callvirt,什么时候编译成call,可以看 .net Framework 程序设计,
对虚方法编译为IL时候,一般使用callvirt,除非base.虚方法(), 值类型.虚方法() , Sealed Instance.虚方法()
比如值类型override了ToString(),调用的时候会使用call,不会被装箱。
对于非虚方法,可能会抛nullreferenceexception,会使用call

转载于:https://www.cnblogs.com/redpeachsix/archive/2007/07/11/814362.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值