call vs callvirt; 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:
class Summary2
{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 Base16
{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 : Base28
{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 ([0] class 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
class Summary2
{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 Base17
{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 : Base29
{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的阻断情况,但仍然符合这个规律。
class Summary2
{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 Base21
{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 : Base34
{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 : DerivedLayer149
{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) 收藏 举报

浙公网安备 33010602011771号