我本来以为委托很简单,本来只想简简单单的说说委托背后的东西,委托的使用方法。原本只想解释一下那句:委托是面向对象的、类型安全的函数指针。可没想到最后惹出一堆的事情来,越惹越多,罪过,罪过。本文后面一部分是我在一边用SOS探索一边记录的,写的非常糟糕,希望您的慧眼能发现一些有价值的东西,那我就感到无比的荣幸了。
委托前世与今生
大家可能还记得,在C/C++里,我们可以在一个函数里实现一个算法的骨架,然后在这个函数的参数里放一个“钩子”,使用的时候,利用这个“钩子” 注入一个函数,注入的函数实现不同算法的不同部分,这样就可以达到算法骨架重用的目的。而这里所谓的“钩子”就是“函数指针”。这个功能很强大啊,但是函数指针却有它的劣势:不是类型安全的、只能“钩”一个函数。大家可能都知道微软对委托的描述:委托是一种面向对象的,类型安全的,可以多播的函数指针。要理解这句话,我们先来看看用C#的关键字delegate声明的一个委托到底是什么样的东西:
MyDelegate(TestDelegateAssignDelegate()Test(GetTarget();._target;MyDelegate(TestDelegateTest(CallByDelegate()Main()CallByDelegate() 2: { 3: MyDelegate myDelegate = new MyDelegate(this.Test); 4: 5: myDelegate(5); 6: }再来看看其对应的IL代码:
1: .method public hidebysig instance void CallByDelegate() cil managed 2: { 3: // Code size 21 (0x15) 4: .maxstack 3 5: .locals init ([0] class Yuyijq.DotNet.Chapter2.MyDelegate myDelegate) 6: IL_0000: ldarg.0 7: IL_0001: ldftn instance void Yuyijq.DotNet.Chapter2.TestDelegate::Test(int32) 8: IL_0007: newobj instance void Yuyijq.DotNet.Chapter2.MyDelegate::.ctor(object, native int) 9: IL_000c: stloc.0 10: IL_000d: ldloc.0 11: IL_000e: ldc.i4.5 12: IL_000f: callvirt instance void Yuyijq.DotNet.Chapter2.MyDelegate::Invoke(int32) 13: IL_0014: ret 14: }前面的代码我们已经熟悉,最关键的就是
callvirt instance void Yuyijq.DotNet.Chapter2.MyDelegate::Invoke(int32)
我们发现,通过委托调用方法,实际上就是调用委托的Invoke方法。
多播的委托
好了,既然已经解释了面向对象和类型安全,那么说委托是多播的咋解释?
你可能已经发现,MyDelegate继承自MulticastDelegate,看这个名字貌似有点意思了。来看看下面这两行代码:
1: MyDelegate myDelegate = new MyDelegate(this.Test); 2: myDelegate += new MyDelegate(this.Test1);通过IL我们可以发现,这里的+=最后就是调用System.Delegate的Combine方法。而Combine的真正实现时在 MulticastDelegate的CombineImpl方法中。在MulticastDelegate中有一个_invocationList字 段,从CombineImpl中可以看出这个字段是一个object[]类型的,而委托链就放在这个数组里。
后记
文章是想到哪儿写到哪儿,写的比较乱,也比较匆忙。非常抱歉。对于中间那段奇妙的事情,我原来真的不知道,我一直以为当委托指向一个静态方法 时,_target指向null就完事儿了,没想到还有这么一番景象。看来很多东西还是不能想当然,亲身尝试一下才知道真实的情况。
原文: