我觉得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:

2



3

4



5

6

7

8

9

10

11

12

13

14

15

16



17

18



19

20

21

22

23



24

25

26

27

28



29

30



31

32

33

34

那么结果是什么呢?
result1:
Non Virtual Base 000
VirtualWork DerivedLayer1 111
结果正如我们料想的那样,很简单,

我们看看Main函数的IL:






















对于
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

2



3

4



5

6

7

8

9

10

11

12

13

14

15

16

17



18

19



20

21

22

23

24



25

26

27

28

29



30

31

32



33

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的阻断情况,但仍然符合这个规律。

2



3

4



5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21



22

23



24

25

26

27

28



29

30

31

32

33

34



35

36



37

38

39

40

41

42



43

44

45

46

47

48

49



50

51



52

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