CLR 提供兩種不同的 IL 指令來呼叫 method
--------------------------------------------------------------------------
[call]
call 可以用來呼叫所有型態的 method.
透過 call 來呼叫 static method 時,
必須標明 class, 例如:
call void ConsoleApplication1.Sample::StaticMethod()
透過 call 來呼叫 instance 或 virtual method 時,
必須提供一個變數, 用來參考該物件, 例如:
.local init(
[0] class ConsoleApplication1.Base base2,
[1] int32 num
)
...
ldloc.0
call instance void ConsoleApplication1.Base::InstanceMethod()
CLR 會根據變數所宣告的型別 (而非被參考的物件的型別),
去找找看該型別有沒有定義該 method,
若無, 則會到他的父代去找, 直到找到為止. (或找不到就丟例外)
另外, call 不會去檢查該變數的值是否為 null.
--------------------------------------------------------------------------
[callvirt]
callvirt 可以用來呼叫 instance 或 virtual method,
但不能用來呼叫 static method.
透過 callvirt 來呼叫 instance 或 virtual method 時,
也必須提供一個變數, 用來參考該物件, 例如:
.local init(
[0] class ConsoleApplication1.Base base2,
[1] int32 num
)
...
ldloc.0
callvirt instance void ConsoleApplication1.Base::InstanceMethod()
callvirt 會先檢查該變數的值是不是 null (是的話直接丟例外),
如果被 call 的是 instance method,
則 callvirt 接下來的行為和 call 一模一樣.
但若被 call 的是 virtual method,
則會以被參考物件的實際型別為起點, 往父代找符合的 method.
--------------------------------------------------------------------------
參考下面的 code
using System;
using System.Text;
namespace ConsoleApplication1 {
class Base {
public void InstanceMethod() {
Console.WriteLine( "Base.InstanceMethod()" );
}
public virtual void VirtualMethod() {
Console.WriteLine( "Base.VirtualMethod()" );
}
public static void StaticMethod() {
Console.WriteLine( "Base.StaticMethod()" );
}
};
class Drived : Base {
public new void InstanceMethod() {
Console.WriteLine( "Drived.InstanceMethod()" );
}
public override void VirtualMethod() {
Console.WriteLine( "Drived.VirtualMethod()" );
}
public new static void StaticMethod() {
Console.WriteLine( "Drived.StaticMethod()" );
}
};
class Program {
static void Main( string[] args) {
Base obj = new Drived();
obj.InstanceMethod();
obj.VirtualMethod();
Drived.StaticMethod();
Int32 i = 5;
i.ToString();
}
}
}
以下是 Main 的 IL
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 1
.locals init (
[0] class ConsoleApplication1.Base base2,
[1] int32 num)
L_0000: newobj instance void ConsoleApplication1.Drived::.ctor()
L_0005: stloc.0
L_0006: ldloc.0
L_0007: callvirt instance void ConsoleApplication1.Base::InstanceMethod()
L_000c: ldloc.0
L_000d: callvirt instance void ConsoleApplication1.Base::VirtualMethod()
L_0012: call void ConsoleApplication1.Drived::StaticMethod()
L_0017: ldc.i4.5
L_0018: stloc.1
L_0019: ldloca.s num
L_001b: call instance string [mscorlib]System.Int32::ToString()
L_0020: pop
L_0021: ret
}
注意幾個令人意外的地方:
首先, C# 居然用 callvirt 來呼叫 InstanceMethod!!!
這是因為 C# Compiler 認為檢查變數是否為 null 是很重要的.
其次, Int32.ToString()是一個 virtual method,
他 override 的 Object.ToString(),
但 C# Compiler 卻使用 call 來呼叫他!!!
這是因為 Int32 並不能被繼承,
而且變數本身的型別就是 Int32,
因此使用 call 不會造成問題, 這算是一種最佳化的方法.
--------------------------------------------------------------------------
另外一種情況會使得 C# Compiler 使用 call 來呼叫 virtual method,
參考以下程式碼:
class Dummy {
public override string ToString() {
return base.ToString();
}
}
他的 IL 長這樣:
.class auto ansi nested private beforefieldinit Dummy
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib]System.Object::.ctor()
L_0006: ret
}
.method public hidebysig virtual instance string ToString() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance string [mscorlib]System.Object::ToString()
L_0006: ret
}
}
注意 C# Compiler 使用 call 來呼叫 base.ToString().
理由是如果用 callvirt 的話會造成無窮遞迴.
出處: CLR via C# 4Ed Chapter 6