委托

在C++中,函数指针是一个指向内存地位的指针,它不是类型平安的,咱们无奈判断这个指针理论指向什么,包含参数和返回类型也无从通晓。而.NET委托齐全不同,委托类型是平安的,是用户自定义的存储了一系列具备雷同签名和返回类型的办法的地址的自定义类型。不仅蕴含对办法的援用,也能够蕴含对多个办法的援用。

申明委托
delegate void m_CustomDelegate(string msg);

这段代码应用delegate关键字申明了一个委托类型,它的实例将会援用一个返回类型为void并且承受一个string类型参数的办法。

委托的应用
var testDel = new CustomDelegate(StaticDelegate);//将会在控制台打印输出:msg : 回调静态方法testDel("回调静态方法");testDel.Invoke("回调静态方法");static void StaticDelegate(string msg){    Console.WriteLine("msg : {0}", msg);}
协变性和逆变性
  • 协变性:办法能够返回从委托的返回类型派生的一个类型
  • 逆变性:办法获取的参数能够是委托的参数类型的基类

示例代码:

delegate object CallBack(FileStream stream);//正确var callback = new CallBack(CallMethod);static string CallMethod(Stream stream){    return string.Empty;}

CallMethod的返回string类型派生自委托类型的object,合乎协变性;CallMethod的参数类型Stream是委托的参数类型FileStream的基类,合乎逆变性。

不合乎协变性或逆变性的无奈和委托绑定
delegate object CallBack(FileStream stream);//谬误:CallMethod返回类型谬误var callback = new CallBack(CallMethod);static int CallMethod(Stream stream){    return 0;}
回调实例办法
var testDel1 = new CustomDelegate(new Program().InstanceDelegate);testDel1.Invoke("回调实例办法");private void InstanceDelegate(string msg){    Console.WriteLine("msg : {0}", msg);}

*回调实例办法,委托须要晓得办法操作的是哪个对象的实例

CLR如何实现委托

应用ILDasm.exe查看生成的程序集,如下

通过查看IL代码能够晓得,在申明委托CallBack的时候,编译器其实会主动生成如下的一个类:

internal class CallBack : MulticastDelegate{    //结构器    public CallBack(Object object, IntPtr method);    //与源代码调用办法统一    public virtual void Invoke(System.IO.FileStream);    //以下两个办法实现对回调办法的异步回调    public virtual IAsyncResult     BeginInvoke(System.IO.FileStream, System.AsyncCallback callback, Object object);    public virtual void EndInvoke(System.IAsyncResult result);    }

所有委托类型都派生自MulticastDelegate,所以天然继承了父类的字段、属性和办法,其中有三个公有字段尤为重要:

  • _target字段(System.Object类型):当委托绑定静态方法时,这个值为null。绑定实例办法时,该字段将会援用回调办法要操作的对象
  • _methodPtr字段(System.Intptr类型):外部整数值,CLR用来标记要回调的办法
  • _invocationList(System.Object类型):通常为null。在结构委托链时将会援用一个委托数组

*所有委托都有一个承受两个参数(对象援用和援用回调办法的一个整数)的结构器。

C#晓得要结构的类型是委托的时候,将会剖析源代码以确定援用的是哪个对象和办法。对象的援用被传给结构器的object参数,从MethodDef或MemberRef元数据token取得标识了办法的一个非凡Intptr值传给结构器的method参数。对于静态方法,object参数传递null值。在结构器外部承受的这两个值会别离存在_target和_methodPtr公有字段中。而_invocationList字段也会被设置为null。

Delegate的公共实例属性:Target和Method
  • Target:返回_target的值,如果为静态方法返回null
  • Method:外部将_methodPtr转换成MethodInfo对象并返回
委托链(多播委托)

委托链是由委托对象形成的一个汇合,利用这一点能够调用汇合中的所有办法。

delegate void CustomDelegate(string msg);CustomDelegate delegates = null;var delegate1 = new CustomDelegate(StaticDelegate);var delegate2 = new CustomDelegate(StaticDelegate);var delegate3 = new CustomDelegate(StaticDelegate);delegates += delegate1;delegates += delegate2;delegates += delegate3;delegates.Invoke("多播委托");static void StaticDelegate(string msg){    Console.WriteLine("msg : {0}", msg);}

代码剖析:

  • 首先申明delegates并赋值为null,再申明三个变量delegate1、delegate2、delegate3
  • 接着应用+=运算符能够将delegate1增加到委托链中并且返回delegate1援用的委托对象
  • 再次应用+=运算符增加第二个委托,此时因为delegates曾经援用了一个委托对象,所以合并操作会结构一个新的委托对象。该委托对象初始化的时候_invocationList字段将不再是null,而是援用一个委托对象的数组。0索引被初始化为delegates以后所援用的委托,1索引初始化为delegate2援用的委托。最初delegates将被设为新建的援用对象
  • 当再次执行+=运算符增加委托时,将会反复上一步骤,即:结构新的委托对象,初始化_invocationList字段索引0,1,2别离援用delegate1,delegate2,delegate3援用的委托,最初delegates被设为新建的援用对象
  • 最初再执行delegates.Invoke时,该委托发现公有字_invocationList不为null,便会执行一个循环遍历数组中的所有元素,并顺次调用每一个办法
删除委托
delegates -= delegate2;

删除委托时的操作:

  • 从开端向0索引扫描delegates外部的委托数组
  • 查找_target和_methodPtr字段与delegate2相匹配的委托将其删除
  • 删除后数组仅剩一个数据项就间接返回该数据项,否则新建一个委托对象,并初始化_invocationList数组将援用原数组中的所有数据项,返回新的委托对象的援用
  • 当将最初一个数据项也删除后,返回null

*每次删除只能删除与_target和_methodPtr匹配的一个委托,而不是删除与之匹配的所有委托

委托链的局限性与解决方案
  • 局限:如果其中一个委托执行谬误,产生异样,后续的所有委托都将无奈调用。
  • 应用GetInvocationList办法实现自定义遍历委托调用
CustomDelegate delegates = null;delegates += new CustomDelegate(StaticDelegate1);delegates += new CustomDelegate(StaticDelegate2);delegates += new CustomDelegate(StaticDelegate3);var index = 0;foreach (CustomDelegate fn in delegates.GetInvocationList()){    index++;    try    {        fn.Invoke(string.Format("第{0}个委托", index));    }    catch (Exception exp)    {        Console.WriteLine(exp.Message);    }}static void StaticDelegate1(string msg){    Console.WriteLine("msg : {0}", msg);}static void StaticDelegate2(string msg){    throw new Exception(string.Format("{0} >> 执行谬误", msg));}static void StaticDelegate3(string msg){    Console.WriteLine("msg : {0}", msg);}

不会再因为其中某个委托产生异样而卡住程序,后果如下