关于c#:委托

32次阅读

共计 3914 个字符,预计需要花费 10 分钟才能阅读完成。

委托

在 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);
}

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

正文完
 0