如前,还是转载的,加入自己的笔记,用以回忆温习。
引言:
上一个专题已经和大家分享了我理解的——C#中为什么需要委托,专题中简单介绍了下委托是什么以及委托简单的应用的,在这个专题中将对委托做进一步的介绍的,本专题主要对委本质和委托链进行讨论。
一、委托的本质
平时我们很容易使用委托——用C# delegate关键字定义委托,再用new操作符构造委托实例,然后通过调用委托实例来调用回调方法(就是用了一个委托对象的变量来代替方法名,这句话如果刚接触的人不好理解的话,这里给个例子:MyDelegate mydelegate =new Mydelegate(obj.mymethod),MyDelegate 是定义(定义吗?严格来讲是声明加初始化吧!)的一个委托,假设定义的是没有参数的,然后调用委托实例是这样的——mydelegate(), 大家可以发现此时调用委托和调用方法的方式是一模一样的,如果没有看前面mydelegate是个委托类型,大家都会认为这是直接调用一个方法,而不是调用委托实例,通过这个例子大家应该很容易明白了这句话了吧——用一个委托对象的变量来代替方法名),相信通过括号内的讲解后,相信大家又会对委托有进一步的理解的——委托就是方法的代替品,委托变量此时代表着方法名,大家可以简单理解委托是方法的一个 “外号”。
前面的都介绍了委托的一些使用和理解的,现在就让我我们来进一步看看编译器和CLR在背后对我们用delegate 关键字定义的委托类型做了些什么事情的,前一个专题中我和大家说过委托是一个类,这么是有根据的,因为我们在IDE中定义一个委托类型时,最终是通过编译器将定义的代码转化为中间语言IL,然后再执行中间语言中的代码来转化为本机代码的,所以在Visual Studio中编写的代码只是一个包装而已,其实程序执行的是中间语言中的代码的。现在就看看编译器把我们定义的委托类型转化为什么样的中间语言代码的。
当我们在类中像下面这样定义一个委托时:
public delegate void DelegateTest(int parm);
编译器把我们定义的委托类型编译成一个下面这样的类:
1 Public class DelegateTest: System.MulticastDelegate 2 { 3 public DelegateTest(Object object, IntPtr method); 4 5 public virtual Void Invoke(int32 parm); 6 7 public virtual IAsyncResult BeginInvoke(Int32 parm, AsyncCallback callback, Object object ); 8 9 public virtual void EndInvoke(IAsyncResult result); 10 }
从中间语言的代码就可以很明显的看出我们在代码中写的委托,对于中间语言来说就是一个类,该类继承于FCL中定义的Systme.MulticastDelegate类型,所有委托类型都派生于MulticastDelegate,该类中还定义了四个方法,一个构造函数,Invoke方法,还有就是两个异步方法BeginInvoke和EndInvoke方法,关于这两个异步方法,大家可以查看我博客中的线程系列。大家可以用ILDasm.exe工具去查看委托生成的中间代码,下面我截的一个图(从我们定义的DelegateTest的前面的图标和我们主程序传递Program的图标是一样的,然而Program是一个类,很明显定义的委托DelegateTes也是一个类的):
由于所有委托类型都是继承于MulticastDelegate,MulticastDelegate又继承于Delegate,所以委托类型继承了MulticastDelegate的字段、属性和方法,在这些成员中,有三个非公共字段与后面专题要介绍的委托链有关,所以在这里先列出来的:
字段 |
类型 |
解释 |
_target |
System.Object |
当委托对象包装的是一个静态方法时,这个字段为null,当委托对象包装一个实例方法时,这个字段引用的是方法所在的类的对象 类似于类型对象指针呢 |
_methodPtr |
System.IntPtr |
一个内部的整数,可以认为是方法句柄,标识着要调用的方法 |
_invocationList |
System.Object |
该字段通常为null,当构造一个委托链(多播委托)时,才引用一个委托数组。具体下一部分讲解。 |
大部分人可能会有这么个疑问,既然是非公共字段,所以在MSDN上是看不到的,那我是怎么知道有这三个字段的呢?大家可以通过Reflector工具是反编译查看源码,Multicastdelegate 类通过MSDN查找可以知道该类的命名空间和程序集,这样就可以更具程序集和命名空间用Reflector工具查看Multicastdelegate类的源码,下面是我用Reflector这个工具查看到的源码截图:
从截图中可以看出MulticastDelegate 类中只有两个字段,却没有前面表格中列出的_methodPtr和_target字段的,这两个字段是定义在Delegate类中,大家使用Reflector工具来查看的,这里就不具体贴图了,文章最后会给出Reflector工具下载链接的。
委托对象就是一个包装器,包装了一个方法和调用该方法时要操作的对象,例如,执行下面的代码时:
1 public class Program 2 3 { 4 5 // 声明一个委托类型,它的实例引用一个方法 6 7 // 该方法回去一个int 参数,返回void类型 8 9 public delegate void DelegateTest(int parm); 10 11 public static void Main(string[] args) 12 { 13 14 // 用静态方法来实例化委托 15 DelegateTest dtstatic = new DelegateTest(Program.method1); 16 17 // 用实例方法来实例化委托 18 DelegateTest dtinstance = new DelegateTest(new Program().method2); 19 20 } 21 22 private static void method1(int parm) 23 { 24 25 Console.WriteLine("调用的是静态方法,参数值为:" + parm); 26 27 } 28 29 private void method2(int parm) 30 { 31 32 Console.WriteLine("调用的是实例方法,参数值为:" + parm); 33 34 } 35 }
代码中dtstatic 和dtinstance变量引用了初始化好的DelegateTest委托对象,此时这两个委托对象的上面列出来的三个字段初始化情况如下图:原图画错了 找找!我没有更正。
二、总结
本专题从中间语言的角度去详细解析定义的委托类型经编译器转化后的的中间语言是怎样来解释一个委托类型的,得到的结论是——委托实际上是一个类,该类派生于MulticastDelegate类,且继承了该类的_target,_methodPtr和_invocationList这三个字段,当我们初始化一个委托对象时,此时就会先初始化这三个字段,对于包装实例方法和静态方法的委托,初始化这三个字段也有所不一样,在上面的截图中也所体现,这里引用了一个很重要的字段——_invocationList(即委托实例的调用列表),对于委托对象包装一个方法时,该字段为null,如果委托对象要包装多个方法时,此时_invocationList字段就会被初始化为引用一个委托对象的数组(就是指向委托对象的一个集合),具体这方面的内容将在下一专题介绍委托链中为大家详细介绍。 到这里,本专题的内容也结束了,希望通过本专题,大家可以更进一步的理解C#中的委托。