如果你对于.NET 中间语言有一定了解,你一定注意到 CIL 提供了2个方法调用指令:call 和 callvirt。本博文将简要介绍着2个指令并让你对它们的使用有一个大致的了解。
call 指令在 CIL 中提供基本的指令调用功能,让我们看看下面的例子:
class Program
{
static void Main(string[] args)
{
Printer.Print("Hello World");
}
}
public class Printer
{
public static void Print(string message)
{
Console.WriteLine(message);
}
}
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// 代码大小 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Hello World"
IL_0006: call void CSharp.CLR.Program/Printer::Print(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::Main
.method public hidebysig static void Print(string message) cil managed
{
// 代码大小 9 (0x9)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call void [mscorlib]System.Console::WriteLine(string)
IL_0007: nop
IL_0008: ret
} // end of method Printer::Print
也许区分 call 和 callvirt 指令最简单的方法就是查看 CIL 文档,其中 call 只是简单得用来"调用由传递的方法说明符指示的方法",而 callvirt 是用来 “对对象调用后期绑定方法,并且将返回值推送到计算堆栈上”。为了理解这两段描述的不同,我们来看一个例子:
public static void Print(object thingy)
{
Console.WriteLine(thingy.ToString());
}
.method public hidebysig static void Print(object thingy) cil managed
{
// 代码大小 14 (0xe)
.maxstack 8
IL_0000: nop
IL_0001: ldarg.0
IL_0002: callvirt instance string [mscorlib]System.Object::ToString()
IL_0007: call void [mscorlib]System.Console::WriteLine(string)
IL_000c: nop
IL_000d: ret
} // end of method Printer::Print
到目前为止,我们讨论了 call 指令和 callvirt 指令的不同,call 指令提供了简单的方法调用功能,而 callvirt 指令则提供多态得调用虚方法的能力。然而,如果你开始检查你自己的 C# 程序,你将会发现 callvirt 指令也被用来调用非虚实例方法。那么 C# 编译器为什么要这么做?以下是两个理由:
public override string ToString()
{
base.ToString();
}