之前在Emit的学习过程中,多次碰到了方法的调用,发现有时候是使用Call而有时候是使用Callvirt,一直对这两者的区别不甚了解。然后就查阅了MSDN,MSDN中对这两者的解释为:
l
Call:调用由传递的方法说明符指示的方法;
l
Callvirt:对对象调用后期绑定方法,并且将返回值推送到计算堆栈上。
但是看了之后还是很不明白,我想可能是因为中文版的缘故吧。今天下午再次看到了对Callvirt指令的解释,“对对象调用后期绑定方法”,突然想到,这个好像是指多态的意思吧?在一看virt,应该就是virtual的缩写,于是就更加肯定了自己的想法(外派在农行,不能上网,不然在园子随便一找就有结果了,伤心啊!),立马动手开始实践。
我们用最经典的Animal的例子来验证这个想法,首先定义相关的类型,如下:
Animal
class Animal
{
public virtual void Speak()
{
Console.WriteLine("Animal.Speak");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Cat.Speak");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Dog.Speak");
}
}
由于只是实现简单的方法调用,所以我们在这里选择使用DynamicMethod而不再创建动态程序集,顺便也可以演练下DynamicMethod的使用。要使用DynamicMethod我们首先要定义一个委托,用来执行方法的调用,定义如下:
private delegate
void SpeakDelegate(Animal animal);
到时候我们通过此委托,传入一个Animal类或者其派生类的实例,并调用里面的Speak方法,从而验证之前的想法。由于方法的实现比较简单,这里就直接通过代码的注释进行讲解,代码如下:
DynamicMethod
class Program
{
private delegate void SpeakDelegate(Animal animal);
static void Main(string[] args)
{
//定义动态方法,没有返回值,传入参数为Animal,所在的模块选择为Program类所在的模块
DynamicMethod dynamicSpeakWithCall = new DynamicMethod("DynamicSpeakWithCall", null, new Type[] { typeof(Animal) }, typeof(Program).Module);
ILGenerator callIL = dynamicSpeakWithCall.GetILGenerator();
//加载参数0 即 Animal类或其派生类的对象
callIL.Emit(OpCodes.Ldarg_0);
//通过Call指令调用Speak方法
callIL.Emit(OpCodes.Call, typeof(Animal).GetMethod("Speak"));
callIL.Emit(OpCodes.Ret);
Console.WriteLine("SpeakWithCall:");
SpeakDelegate SpeakWithCall = (SpeakDelegate)dynamicSpeakWithCall.CreateDelegate(typeof(SpeakDelegate));
SpeakWithCall(new Animal());
SpeakWithCall(new Cat());
SpeakWithCall(new Dog());
//定义动态方法,没有返回值,传入参数为Animal,所在的模块选择为Program类所在的模块
DynamicMethod dynamicSpeakWithCallvirt = new DynamicMethod("DynamicSpeakWithCallvirt", null, new Type[] { typeof(Animal) }, typeof(Program).Module);
ILGenerator callvirtIL = dynamicSpeakWithCallvirt.GetILGenerator();
//加载参数0 即 Animal类或其派生类的对象
callvirtIL.Emit(OpCodes.Ldarg_0);
//通过Callvirt指令调用Speak方法
callvirtIL.Emit(OpCodes.Callvirt, typeof(Animal).GetMethod("Speak"));
callvirtIL.Emit(OpCodes.Ret);
Console.WriteLine("SpeakWithCallvirt:");
SpeakDelegate SpeakWithCallvirt = (SpeakDelegate)dynamicSpeakWithCallvirt.CreateDelegate(typeof(SpeakDelegate));
SpeakWithCallvirt(new Animal());
SpeakWithCallvirt(new Cat());
SpeakWithCallvirt(new Dog());
}
}
最后给出相应的输出结果:
SpeakWithCall:
Animal.Speak
Animal.Speak
Animal.Speak
SpeakWithCallvirt:
Animal.Speak
Cat.Speak
Dog.Speak
|
很明显,之前的推论是正确的,源码下载 Call和Callvirt的区别
。
PS:由于学习Emit才只有几天的时间,所以上面的分析都显得有点肤浅,只是简单的记录下自己的学习过程,如果各位看官能够给我一点深层次的分析,我将不甚感激。