实际上你还可以再编写一个与MyDelegate相同签名的委托,然后也指向一个静态方法,使用相同的方法查看该委托的_methodPtr的值,你会发现这个新委托与MyDelegate的_methodPtr的值是一致的。
刚才不是说这个时候_methodPtr指向的是一个Stub么,既然如此那我们反汇编一下代码:
!u 007809C4
Unmanaged code
007809C4 8BC1 mov eax,ecx
007809C6 8BCA mov ecx,edx
007809C8 83C010 add eax,10h
007809CB FF20 jmp dword ptr [eax]
........
.Net里JIT的方法的调用约定是Fast Call,对于Fast Call来说,方法的前两个参数会放在ECX和EDX两个寄存器中。那么mov eax,ecx实际上就是将_target传递给eax,再看看
704bb188 4000102 10 System.IntPtr 1 instance 0025C018 _methodPtrAux
_methodPtrAux的偏移是10,这里的add eax,10h就是将eax指向_methodPtrAux,然后jmp dword ptr[eax]就是跳转到_methodPtrAux所指向的地址了,就是委托指向的那个静态方法。
通过委托调用方法
如何通过委托调用方法呢:
{
MyDelegate myDelegate = new MyDelegate(this.Test);
myDelegate(5);
}
再来看看其对应的IL代码:
{
// Code size 21 (0x15)
.maxstack 3
.locals init ([0] class Yuyijq.DotNet.Chapter2.MyDelegate myDelegate)
IL_0000: ldarg.0
IL_0001: ldftn instance void Yuyijq.DotNet.Chapter2.TestDelegate::Test(int32)
IL_0007: newobj instance void Yuyijq.DotNet.Chapter2.MyDelegate::.ctor(object, native int)
IL_000c: stloc.0
IL_000d: ldloc.0
IL_000e: ldc.i4.5
IL_000f: callvirt instance void Yuyijq.DotNet.Chapter2.MyDelegate::Invoke(int32)
IL_0014: ret 14: }
前面的代码我们已经熟悉,最关键的就是
callvirt instance void Yuyijq.DotNet.Chapter2.MyDelegate::Invoke(int32)
我们发现,通过委托调用方法,实际上就是调用委托的Invoke方法。
多播的委托
好了,既然已经解释了面向对象和类型安全,那么说委托是多播的咋解释?
你可能已经发现,MyDelegate继承自MulticastDelegate,看这个名字貌似有点意思了。来看看下面这两行代码:
myDelegate += new MyDelegate(this.Test1);
通过IL我们可以发现,这里的+=最后就是调用System.Delegate的Combine方法。而Combine的真正实现时在MulticastDelegate的CombineImpl方法中。在MulticastDelegate中有一个_invocationList字段,从CombineImpl中可以看出这个字段是一个object[]类型的,而委托链就放在这个数组里。
后记
文章是想到哪儿写到哪儿,写的比较乱,也比较匆忙。非常抱歉。对于中间那段奇妙的事情,我原来真的不知道,我一直以为当委托指向一个静态方法时,_target指向null就完事儿了,没想到还有这么一番景象。看来很多东西还是不能想当然,亲身尝试一下才知道真实的情况。