当前位置: 首页 > 编程日记 > 正文

[C#1] 9-委托

委托揭秘

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

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

看看IL:

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

字段类型描述
_targetSystem.object指向回调函数被调用时应该操作的对象,用于实例方法的回调
_mothodPtrInt32一个内部的整数值,CLR用它来标识回调函数
_prevSystem.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

相关文章:

LeetCode实战:环形链表

题目英文 Given a linked list, determine if it has a cycle in it. To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycl…

Symbian开发系列 - 入门篇

要开始我的Symbian开发之旅了, 先收集一些相关资料,如Symbian概述, 开发平台搭建, 参考书籍与网络资源. 【基础】 什么是Symbian学习Symbian的基本概念 Symbian操作系统 Symbian 入门 【转】symbian操作系统 入门篇 symbian 术语表 S60/Symbian应用程序常用架构/框架S60十大优秀…

软件性能测试主要看什么指标

性能测试不同于功能测试,功能测试只要求软件的功能实现即可,而性能测试是测试软件功能的执行效率是否达到要求。例如某个软件具备查询功能,功能测试只测试查询功能是否实现,而性能测试却要求查询功能足够准确、足够快速。但是&…

LeetCode实战:合并K个排序链表

题目英文 Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. Example: Input: [1->4->5,1->3->4,2->6 ] Output: 1->1->2->3->4->4->5->6题目中文 合并 k 个排序链表,…

4月《程序员》上我讲HTML5的文章---激动人心的HTML5之美

这篇文章分为四个方面介绍了激动人心的HTML5之美: 语义之美人性之美简单之美实用之美欢迎大家阅读。转载于:https://www.cnblogs.com/android-html5/archive/2011/04/08/2533758.html

Golang中Buffer高效拼接字符串以及自定义线程安全Buffer

本文原创文章,转载注明出处,博客地址 https://segmentfault.com/u/to... 第一时间看后续精彩文章。觉得好的话,顺手分享到朋友圈吧,感谢支持。Go中可以使用“”合并字符串,但是这种合并方式效率非常低,每合并一次,都是创建一个新的字符串,就必…

Python培训之就业面试题分享

近几年,学习Python编程的人越来越多,大家对于Python编程技术非常感兴趣,想要转型到这个行业,下面小编为大家整理一份Python找工作的面试题分享,希望能够帮助正在找Python工作的小伙们。 Python培训之就业面试题分享&am…

IHttpHandler 概述

IHttpHandler 概述可能和我一样,很多Asp.Net开发人员都有过Asp的背景,以至于我们在开发程序的时候,通常都是在“页面级”上思考,也就是说我们现在正在做的这个页面应该有什么样的功能,是进行一个问卷调查还是一个数据库…

LeetCode实战:有效的括号

题目英文 Given a string containing just the characters ‘(’, ‘)’, ‘{’, ‘}’, ‘[’ and ‘]’, determine if the input string is valid. An input string is valid if: Open brackets must be closed by the same type of brackets.Open brackets must be clos…

linux下的vi与vim

vi与vimvi编辑器是所有Unix及Linux系统下标准的编辑器,他就相当于windows系统中的记事本一样,它的强大不逊色于任何最新的文本编辑器。他是我们使用Linux系统不能缺少的工具。由于对Unix及Linux系统的任何版本,vi编辑器是完全相同的&#xff…

零基础该如何学习Web前端知识?

想要跳槽到IT行业人在近几年越来越多,大部分都是想要学习web前端技术,但是这其中有很多都是零基础学员,大家都想知道零基础该如何学习Web前端知识?我们来看看下面的详细介绍。 零基础该如何学习Web前端知识? 1、Web前端学习步骤 (1)HTML标签…

刻意练习:Python基础 -- Task05. 函数与Lambda表达式

背景 我们准备利用17天时间,将 “Python基础的刻意练习” 分为如下任务: Task01:变量、运算符与数据类型(1day)Task02:条件与循环(1day)Task03:列表与元组(…

PHP 实现无限分类

最近打算做一个blog,通常每篇文章都有属于自己的分类。下面就记录下我在写blog时实现无限分类的过程。php框架用的是laravel,根据注释也能轻松改成你习惯的框架。 数据表设计 CREATE TABLE article_category (id int(10) unsigned NOT NULL AUTO_INCREMENT,pid int(…

软件测试培训怎么学?有没有发展前景?

软件测试是最近几年广受大家关注的一个编程技术,软件测试的出现也是因软件的存在而存在的,目前很多人都想知道软件测试培训怎么学?有没有发展前景?我们来看看下面的详细介绍。 软件测试需要学测试环境(网络环境,windows环境等)、数据库管理…

LeetCode实战:最长有效括号

题目英文 Given a string containing just the characters ‘(’ and ‘)’, find the length of the longest valid (well-formed) parentheses substring. Example 1: Input: "(()" Output: 2 Explanation: The longest valid parentheses substring is "(…

Android选项卡置底的方法

发现很多Android应用的选项卡 都是显示在页面底部的&#xff0c;网上有资料&#xff1a;通过反射获取TabWidget中的私有变量&#xff0c;改变其值。今天反编译了腾讯微薄&#xff0c;发现实现这个很简单, 只需将布局文件中<TabWidget />标签加个android:layout_gravity&…

【iCore4 双核心板_ARM】例程十七:USB_MSC实验——读/写U盘(大容量存储器)

实验方法&#xff1a; 1、将跳线冒跳至USB_UART,通过Micro USB 线将iCore4 USB-UART接口与电脑相连。 2、打开PUTTY软件。 3、通过读U盘转接线将U盘&#xff08;或者读卡器&#xff09;与iCore4 USB-OTG接口相连。大容量存储器为FAT32格式。 实验现象&#xff1a; 核心代码&…

软件测试技术篇:UI自动化到底是难是易?

UI自动化技术&#xff0c;是我们测试工程师绕不开的一个话题&#xff0c;只要提起它来&#xff0c;基本所有测试工程师都能给你说道说道。 有些人认为它很难&#xff0c;有些人认为它很简单。认为它很难的人会告诉你&#xff0c;UI自动化非常不稳定&#xff0c;太难了&#xff…

获取DataRow某列的值的封装

public class DataHelper{const string DEFSTR "";/// <summary>/// 根据一个类型&#xff0c;获取其默认值&#xff0c;数字默认是为0&#xff0c;字符串默认值为一个空字符串/// </summary>/// <typeparam name"T"></typeparam>…

LeetCode实战:逆波兰表达式求值

题目英文 Evaluate the value of an arithmetic expression in Reverse Polish Notation. Valid operators are , -, *, /. Each operand may be an integer or another expression. Note: Division between two integers should truncate toward zero.The given RPN expre…

Python函数式编程-map/reduce

1.map map()传入的第一个参数是f&#xff0c;即函数对象本身。 map()函数接收两个参数&#xff0c;一个是函数&#xff0c;一个是Interable&#xff0c;map将传入的函数依次作用到序列的每个元素&#xff0c;并把结果作为新的Iterator返回。 >>> def f(x): ... re…

Java程序员到什么级别可以去BAT上班?

学习java技术&#xff0c;很多人都想要进入到IT行业&#xff0c;如果跳槽到BAT大厂上班&#xff0c;那更是非常好的&#xff0c;近几年学习java技术的人越来越多&#xff0c;那么Java程序员到什么级别可以去BAT上班?来看看下面的详细介绍。 Java程序员到什么级别可以去BAT上班…

Android开发之SharedPreferences的封装

对于大部分初学者来说&#xff0c;如果想利用SharedPreferences进行数据存储的话大部分人(包括本人)应该会这样&#xff1a; 存储&#xff1a; SharedPreferences sharedPreferences getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); Editor editor …

LeetCode实战:设计循环双端队列

题目英文 Design your implementation of the circular double-ended queue (deque). Your implementation should support following operations: MyCircularDeque(k): Constructor, set the size of the deque to be k.insertFront(): Adds an item at the front of Deque…

ItemsControl 解析

先上个示例 <ItemsControl Margin"10" ItemsSource"{Binding}" Name"itemsControl"> <ItemsControl.Template><ControlTemplate TargetType"{x:Type ItemsControl}"><Border CornerRadius"5">&l…

【Web前端培训基础知识】ES5及ES6this详解

今天&#xff0c;我们学习一下JavaScript中的this。我们从什么是this,ES5及ES6中this的几种情况进行学习。让this变的so easy&#xff0c;我们这里说的都是非严格模式下。 什么是this this表示当前行为执行的主体&#xff0c;在javaScript中this不是函数独有的&#xff0c;但是…

LeetCode实战:滑动窗口最大值

题目英文 Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sli…

Partial Class部分类

Partial Class &#xff0c;部分类 或者分布类。顾名思义&#xff0c;就是将一个类分成多个部分。比如说&#xff1a;一个类中有3个方法&#xff0c;在VS 2005将该类中3个方法分别存放在3个不同的.cs文件中。这样做的好处&#xff1a;1、一个大型的项目类可以同时分成不同的区块…

表格中td限宽溢出以省略号代替

table.ms-listviewtable {table-layout:fixed;width: 100%; } table.ms-listviewtable td[role"gridcell"]{white-space:nowrap;text-overflow:ellipsis;-moz-text-overflow: ellipsis;overflow:hidden; } 转载于:https://www.cnblogs.com/JaneBlog/p/7490445.html

【UI设计培训基础知识】设计中的点线面-线

UI设计所要学习的知识有很多&#xff0c;想要在后期的工作中稳稳当当&#xff0c;基础知识一定要扎实&#xff0c;下面就是小编为大家整理的一份关于UI设计培训基础知识的相关内容&#xff0c;主要讲的是设计中的点线面-线&#xff0c;来看看下面的详细资料吧。 点的移动形成一…