接着看看AssignDelegate反编译后的代码:
{
// Code size 19 (0x13)
.maxstack 8
//将方法的第一个参数push到IL的运算栈上(对于一个实例方法来说,比如AssignDelegate,它的第一个参数就是“this”了)
IL_0000: ldarg.0
//这里又把this压栈了一次,因为下面一条指令中的Test方法是一个实例方法,需要一个this
IL_0001: ldarg.0
//ldftn就是把实现它的参数中的方法的本机代码的非托管指针push到栈上,在这里你就可以认为是获取实例方法Test的地址
IL_0002: ldftn instance void Yuyijq.DotNet.Chapter2.TestDelegate::Test(int32)
//调用委托的构造器,这个构造器需要两个参数,一个对象引用,就是第一次压栈的this,一个方法的地址。
IL_0008: newobj instance void Yuyijq.DotNet.Chapter2.MyDelegate::.ctor(object,native int)
IL_000d: stfld class Yuyijq.DotNet.Chapter2.MyDelegate Yuyijq.DotNet.Chapter2.TestDelegate::myDelegate
IL_0012: ret
}
通过上面的代码,我们会发现,将一个实例方法分配给委托时,委托不仅仅引用了方法的地址,还有这个方法所在对象的引用,这里就是所谓的类型安全。
我们再回过头来看看MyDelegate的继承链:MyDelegate->MulticastDelegate->Delegate。
奇妙的地方
而Delegate中有三个有趣的字段:
internal object _target;
internal IntPtr _methodPtr;
internal IntPtr _methodPtrAux;
对这三个字段做详细说明
_target
1、如果委托指向的方法是实例方法,则_target的值是指向目标方法所在对象的指针
2、如果委托指向的是静态方法,则_target的值是委托实例自身
_methodPtr
1、如果委托指向的方法是实例方法,则_methodPtr的值指向一个JIT Stub(如果这个方法还没有被JIT编译,关于JIT Stub会在后面的章节介绍),或指向该方法JIT后的地址
2、如果委托指向的方法是静态方法,则_methodPtr指向的是一个Stub(一段小代码,这段代码的作用是_target,然后调用_methodPtrAux指向的方法),而且所有签名相同的委托,共享这个Stub。为什么要这样一个Stub?我想是为了让通过委托调用方法的流程一致吧,不管指向的是实例方法还是静态方法,对于外部来说,只需要调用_methodPtr指向的地址,但是对于调用实例方法而言,它需要this,也就是这里的_target,而静态方法不需要,为了让这里的过程一直,CLR会偷偷的在委托指向静态方法时插入一小段代码,用于去掉_target,而直接jmp到_methodPtrAux指向的方法。
_methodPtrAux
1、如果委托指向的是实例方法,则_methodPtrAux就是0。
2、如果委托指向的是静态方法,则这时_methodPtrAux起的作用与_mthodPtr在委托指向实例方法的时候是一样的。
实际上通过反编译Delegate的代码发现,Delegate有一个只读属性Target,该Target的实现依靠GetTarget方法,该方法的代码如下:
{
if (!this._methodPtrAux.IsNull())
{
return null;
}
return this._target;
}
实了当委托指向静态方法时,Target属性为null。