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 class Summary
 2    {
 3        static void Main(string[] args)
 4        {
 5           
 6            Base b = new DerivedLayer1();
 7            b.DoNoVirtualWork();
 8            b.DoVirtualWork();
 9  
10            Console.ReadLine();
11
12        }

13    }

14
15    public class Base
16    {
17        public virtual void DoVirtualWork()
18        {
19            Console.WriteLine("VirtualWork Base 000");
20        }

21
22        public void DoNoVirtualWork()
23        {
24            Console.WriteLine("Non Virtual Base 000");
25        }

26    }

27    public class DerivedLayer1 : Base
28    {
29        public override void DoVirtualWork()
30        {
31            Console.WriteLine("VirtualWork DerivedLayer1 111");
32        }

33
34    }


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

对于
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 class Summary
 2    {
 3        static void Main(string[] args)
 4        {
 5
 6            DerivedLayer1 d1 = new DerivedLayer1();
 7
 8            d1.DoNoVirtualWork();
 9            d1.DoVirtualWork();
10  
11            Console.ReadLine();
12
13        }

14    }

15
16    public class Base
17    {
18        public virtual void DoVirtualWork()
19        {
20            Console.WriteLine("VirtualWork Base 000");
21        }

22
23        public void DoNoVirtualWork()
24        {
25            Console.WriteLine("Non Virtual Base 000");
26        }

27    }

28    public class DerivedLayer1 : Base
29    {
30
31        public new void DoNoVirtualWork()
32        {
33            Console.WriteLine("Non Virtual DerivedLayer1 111");
34        }

35
36    }

37


注意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 class Summary
 2    {
 3        static void Main(string[] args)
 4        {
 5            Base b = new DerivedLayer2();
 6            b.DoNoVirtualWork();
 7            b.DoVirtualWork();
 8
 9            Console.WriteLine("********");
10
11            DerivedLayer1 d1 = new DerivedLayer2();
12            d1.DoNoVirtualWork();
13            d1.DoVirtualWork();
14
15            Console.ReadLine();
16
17        }

18    }

19
20    public class Base
21    {
22        public virtual void DoVirtualWork()
23        {
24            Console.WriteLine("VirtualWork Base 000");
25        }

26
27        public void DoNoVirtualWork()
28        {
29            Console.WriteLine("Non Virtual Base 000");
30        }

31    }

32
33    public class DerivedLayer1 : Base
34    {
35        public new virtual void DoVirtualWork()
36        {
37            Console.WriteLine("VirtualWork DerivedLayer1 111");
38        }

39
40
41        public new void DoNoVirtualWork()
42        {
43            Console.WriteLine("Non Virtual DerivedLayer1 111");
44        }

45
46    }

47
48    public class DerivedLayer2 : DerivedLayer1
49    {
50        public override void DoVirtualWork()
51        {
52            Console.WriteLine("VirtualWork DerivedLayer2 222");
53
54        }

55
56    }

这次的结果会是什么呢?注意"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

posted on 2007-07-11 17:17  redpeachsix  阅读(2570)  评论(5)    收藏  举报

导航