委托揭秘

编译器和CLR在后台做了很多工作来隐藏委托本身的复杂性,如下一句委托声明:

//编译器为我们产生了一个同名的类
public delegate void MyDelegate(int i);

看看IL:

可以看出它默认继承自System.MulticastDelegate[所有委托都继承此类,MulticastDelegate又继承自System.Delegate],我们声明的public,所以编译器产生的类也是public。委托可以定义在类的内部或外部,因为委托本身就是类,所以类可以定义在哪委托就可以定义在哪。System.MulticastDelegate中有几个重要的私有字段:

字段 类型 描述
_target System.object 指向回调函数被调用时应该操作的对象,用于实例方法的回调
_mothodPtr Int32 一个内部的整数值,CLR用它来标识回调函数
_prev System.MulticastDelegate 指向另一个委托对象

所有委托都有这样一个构造器[void .ctor (object,int)],第一个参数是一个对象的引用,第二个是一个指向回调方法的整数。声明如下一个方法:

static void myMothod(int i);
MyDelegate md=new MyDelegate(myMothod);

我们把myMothod传给了MyDelegate的构造函数,但是这和MyDelegate构造函数的参数并不匹配,但是却编译通过了,为什么呢?因为编译器通过分析源代码来确定我们引用的哪个对象和方法,上述myMothod是静态方法,所以会把null传递给target参数, 把一个标识方法的特殊Int32值【由MethodDef或者MethodRef元数据标记获得】给mothodPtr参数; myMothod是实例方法,则会把对象的引用赋给target。在构造器内部,这两个参数会被保存到相应的私有字段中。 另外_prev被设置为null,该对象用来创建一个委托链表[指向下一个委托对象]。

每个委托对象实际上是对方法及其调用时操作的对象的一个封装。 System.MulticastDelegate类有两个只读的共有属性:Target和Method.当给定一个委托对象时,可以根据Target获得一个方法回调时操作的对象引用[静态方法返回null], Method属性返回一个表示回调方法的System.Reflection.MethodInfo对象。

调用回调函数:[ md(6);]看起来像是调用一个方法似得,并且给它一个参数6。实际上并没有md方法,因为编译器知道md是一个指向委托的变量,所以他会产生代码来该委托对象的Invoke方法[让面图片最后一行]. md(6)会被编译为这样一行:

 IL_0014:  callvirt instance void MyDelegate::Invoke(int32)

委托判等

Delegate重写了Object的Equals方法,判断其私有字段_target和_methodPtr字段是否指向同样的对象和方法,相同则返回true。

MulticastDelegate又重写了Delegate的Equals方法,它又加了一项比较,就是_prev字段。如果都为null返回ture;如果都不是null,则查看_prev字段指示的链表是否有指定的长度,并且两个链表上的对应委托对象的_target和_methodPtr字段也是否匹配,如果匹配就返回ture。说白点就是Delegate的Equals判断一个委托对象是否相等,MulticastDelegate的Equals则在Delegate的基础上又增加委托链表的判断。

委托链[_prev]:

每一个MulticastDelegate对象都有一个_prev字段,指向另一个MulticastDelegate对象的引用,则可以构成一个链表。Delegate有3个静态方法来操作委托链表:

 1 public abstract class Delegate : ICloneable, ISerializable
 2 {
 3     //创建一个由委托数组表示的委托链表
 4     public static Delegate Combine(params Delegate[] delegates);
 5
 6     //组合a和b所代笔的链表,并返回b,
 7     public static Delegate Combine(Delegate a, Delegate b);
 8
 9     //从source链表中移除和value匹配的委托【找不到匹配的也不抛异常】
10     //返回新的链表头部
11     public static Delegate Remove(Delegate source, Delegate value);
12
13 }

当一个委托对象被调用时,编译器会产生Invoke方法的调用。伪代码:

public void virtual Invoke(int i)
{if (_prev!=null){_prev.Invoke(i);}_target.MethodPtr(i);
}

可以看出,调用一个委托对象会导致它前面的委托对象首先被调用[ _prev.Invoke(i);], 当前面委托被调用时,其返回值会被丢弃。最后才会调用自己封装的回调目标[_target.MethodPtr(i);]; 应用程序代码只保留了当前委托对象的哪个调用(最后一次用的回调方法)的返回值。

注意:委托对象一旦被创建,它们就被认为是恒定不变的,也就是说委托对象的_prev字段总是null,并且不会改变,当调用Combine将一个新委托对象加到现有委托链中时,Combine方法内部会构造一个新的委托对象,新对象有着和源对象相同的_target和_methodPtr字段,但是其_prev字段会被指向原先委托链表的头部,最后Combine方法返回新委托对象的地址。

Remove方法移除一个委托对象[或者是一个委托链表]。[假如是你想要移除一个委托对象而不是委托链表] 很难看出来它到底是链表还是单独的一个委托对象。最好新创建一个相同的委托对象,新建的委托对象的_prev字段是null,这个null很有用,如下解释:它执行查找委托对象[或者一个委托链表]时,执行内部的一个判断方法【Delegate的Equals方法无法判断委托链表相等性,但是它又无法调用MulticastDelegate类的Equals[不知道这么说对不?], 所以就自己实现一个判等的方法,判等过程同MulticastDelegate类的Equals方法类似,也就是可以判断委托链表相等性了,所以当你移除的一个委托对象恰好是一个委托链的链表头部,则会把它后面指向的委托对象一起移除掉,这恐怕不是我们愿意看到的吧。

Remove方法每次都是从委托链表头开始移除第一个匹配项。C#编译器自动为委托类型提供了+=和-=操作符重载支持, 分别会调用Combine和Remove方法。

对委托链调用施加更多的控制

由于委托类型的Invoke方法具有调用一个委托类型对象之前的委托对象(如果存在)的能力, 但是除了最后一个回调方法的返回值外,其他回调方法的返回值都会丢失,无法得到所有回调方法的返回值。 不仅如此,如果一个被调用的委托链中有一个抛出了异常,或者阻塞了很久其他的委托对象将被阻止调用。 为此MulticastDelegate类提供了一个实例方法GetInvocationList,以数组的形式返回每一个委托对象, 它们的_prev字段都被设置为null,所以每个对象都是孤立的.如下小例子:

 1 class Program
 2 {
 3     public delegate string GetMethodName();
 4     static void Main()
 5     {
 6         GetMethodName gmn = new GetMethodName(Method1);
 7         gmn += new GetMethodName( Method2);
 8         gmn += new GetMethodName( Methoh3);
 9         //测试常规调用结果
10         //输出Method3,其他两个被丢弃
11         Console.WriteLine(gmn());
12
13         //刚知道的Environment.NewLine,试一下,哈哈
14         Console.Write(Environment.NewLine);
15
16         Delegate[] myDelegateList = gmn.GetInvocationList();
17         //输出Method1 Method2 Method3
18         foreach (GetMethodName item in myDelegateList)
19         {
20             Console.WriteLine(item());
21         }
22
23     }
24
25     public static string Method1()
26     {
27         return "Method1";
28     }
29     public static string Method2()
30     {
31         return "Method2";
32     }
33     public static string Methoh3()
34     {
35         return "Method3";
36     }
37 }

转载于:https://www.cnblogs.com/linianhui/archive/2011/04/01/csharp1_delegate.html

[C#1] 9-委托相关推荐

  1. c#委托与事件(二)

    这篇博客是在上篇的基础开始讲述了一下委托的一些用法,首先我举一个例子说明了一下前面章节的知识点,接下来我说了将方法作为参数传递的一个案例,接下来实现了一个委托实现冒泡排序的方法,如果你们和我一样正在学 ...

  2. RanceQuest2_从委托到Lambda_会用(递归数学函数)

    二连发 使用Lambda表达式编写递归函数 --摘自老赵点滴 - 追求编程之美. todo用手敲30遍,搞定--泛型委托,Lambda表达式,简单的数学递归. 遗憾的是,原本希望更进一步做出一个通用的 ...

  3. 利用委托和泛型实现树的常用操作

    在日常开发中,经常遇到对树的操作,我们可以利用泛型和委托对这些树进行操作,这样就不需要每有一个树就要实现相应的功能了. 源码在http://files.cnblogs.com/haiconc/Lang ...

  4. 理解委托(delegate)及为什么要使用委托

    委托:是一种定义方法签名的类型. 当实例化委托时,您可以将其实例与任何具有兼容签名的方法相关联. 您可以通过委托实例调用方法. 上述为官方说法,理解起来比较难,举个生活中的例子: 某人有三子,让他们各 ...

  5. dom元素滚动条高度 js_DOM 事件与事件委托

    点击事件 <div class = 爷爷><div class = 爸爸><div class = 儿子>文字</div></div> &l ...

  6. C#编码实践:使用委托和特性调用指定函数

    2019独角兽企业重金招聘Python工程师标准>>> 建立一个C#控制台应用程序AttributeTest. 建立一个类Operations,代码如下: namespace Att ...

  7. 匹夫细说C#:委托的简化语法,聊聊匿名方法和闭包

    0x00 前言 通过上一篇博客<匹夫细说C#:庖丁解牛聊委托,那些编译器藏的和U3D给的>的内容,我们实现了使用委托来构建我们自己的消息系统的过程.但是在日常的开发中,仍然有很多开发者因为 ...

  8. 【转发】什么时候该用委托,为什么要用委托,委托有什么好处

    好多人一直在问:什么时候该用委托,为什么要用委托,委托有什么好处.... 看完下面的文章你将茅塞顿开..(看不懂的直接TDDTDS) 概念虽然我不喜欢讲太多 我们直接先来YY 个场景:我很喜欢打游戏, ...

  9. Lambda表达式可以被转换为委托类型

    void Main() { //向Users类中增加两人; List<Users> user=new List<Users>{ new Users{ID=1,Name=&quo ...

  10. Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...

最新文章

  1. asp从后台调出的公式怎么参与运算_Excel中使用公式老是出错,这几招帮你轻松解决~...
  2. Nature:希望你在读博士之初就能知道的 20 件事
  3. Python运算符与Pandas方法的映射关系
  4. 微软职位内部推荐-Software Engineer II
  5. Java关键字final、static、this、super使用总结
  6. 中断(interrupted()、isInterrupted())、Executor的中断
  7. (四)Struts2 架构
  8. linux 安装程序丢失链接动态库,Linux安装软件过程中提示缺少动态链接库.so的解决方法...
  9. [react] React组件的构造函数有什么作用?
  10. 初识【jQuery】,入门必看!
  11. 机器学习入门一 ------- 什么是机器学习,机器学习的在实际中的用处
  12. GP学习(三)—How to run a geoprocessing tool
  13. php条件运算符加法器,【加法笔记系列】JS 加法器模拟
  14. 在js中通过location.href方式跳转页面并在路径上传递参数中文乱码解决
  15. 重启计算机可以使用什么组合键,win10系统重启电脑的快捷键是什么呢?
  16. 好用的记事本app推荐 记事待办极简便签
  17. 百度信息流是什么?哪些行业适合投放百度信息流?
  18. VPX信号处理板VPX3U-2DSP-C6678
  19. 推荐引擎上策略的步骤以及查bug的方法
  20. 使用飞信api接口实现短信发送(只能发送好友)

热门文章

  1. 实验报告Linux操作系统基本命令,linux操作系统实验报告全部.doc
  2. 前端 重构时需要注意的事项_驾驶式扫地车的功能特点和使用时需要注意事项...
  3. matlab 如何代码自已标注_MATLAB概述
  4. 5m 云服务器2核4g_华为云服务器2核4G 5M 248一年
  5. benke计算机课程设计,(本科课程设计.doc
  6. mysql 插入删除操作_MySQL——增删改操作
  7. 为什么说C语言和linux是分不开的?
  8. RS485通信如何设计EMC电路?
  9. 力扣(LeetCode)刷题,简单题(第9期)
  10. 红警2Linux版本