通过IL分析C#中的委托、事件、Func、Action、Predicate之间的区别与联系
先说一下个人理解的结论吧:
- delegate是C#中的一种类型,它实际上是一个能够持有对某个方法的引用的类。
 - delegate声明的变量与delegate声明的事件,并没有本质的区别,事件是在delegate声明变量的基础上包装而成的,类似于变量与属性的关系(在IL代码中可以看到每一个delegate声明的事件都对应是私有的delegate声明的变量),提升了安全性。
 - Action 与Func:这两个其实说白了就是系统定义好的Delegate,他有很多重载的方法,便于各种应用情况下的调用。他在系统的System命名空间下,因此全局可见。
 
首先了解一下, ILDasm中图标含义:
该图来自:http://www.cnblogs.com/zery/p/3366175.html
委托创建步骤:
1、用delegate关键字创建一个委托,包括声明返回值和参数类型
2、使用的地方接收这个委托
3、创建这个委托的实例并指定一个返回值和参数类型匹配的方法传递过去
一、事件与委托
新建一个事件委托测试项目:EventDelegateTest
具体代码如下:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
 | 
namespace EventDelegateTest  {      public class TestClass      {          public delegate int delegateAction();          public event delegateAction OnActionEvent;          public delegateAction daNew;      }  } | 
编译代码后,使用 Visual Studio 2010自带的ILDASM.EXE:
打开该dll,可以看到如下信息:
从上图可以看出如下几点信息:
1、委托 public delegate int delegateAction();
在IL中是以类(delegateAction)的形式存在的
.NET将委托定义为一个密封类,派生自基类System.MulticastDelegate,并继承了基类的三个方法
2、public event delegateAction OnActionEvent;
在IL中不仅仅对应event OnActionEvent而且还对应一个field OnActionEvent;
而field OnActionEvent与 public delegateAction daNew生成的field daNew是一样的
都是以字段(field )的形式存在的。
双击event OnActionEvent可以看到如下信息:
在IL中事件被封装成了包含一个add_前缀和一个remove_前缀的的代码段。
其中,add_前缀的方法其实是通过调用Delegate.Combine()方法来实现的,组成了一个多播委托;remove_就是调用Delegate.Remove()方法,用于移除多播委托中的某个委托。
也就是说:事件其实就是一个特殊的多播委托。
那么对于事件进行这一次封装有什么好处呢?
1、因为delegate可以支持的操作非常多,比如我们可以写onXXXChanged += aaaFunc,把某个函数指针挂载到这个委托上面,但是我们也可以简单粗暴地直接写onXXXChanged = aaaFunc,让这个委托只包含这一个函数指针。不过这样一来会产生一个安全问题:如果我们用onXXXChanged = aaaFunc这样的写法,那么会把这个委托已拥有的其他函数指针给覆盖掉,这大概不是定义onXXXChanged的程序员想要看到的结果。
小注:
虽然事件不能直接=某个函数,也不可以直接=null
2、还有一个问题就是onXXXChanged这个委托应该什么时候触发(即调用它所包含的函数指针)。从面向对象的角度来说,XXX改变了这个事实(即onXXXChaned的字面含义)应该由包含它的那个对象来决定。但实际上我们可以从这个对象的外部环境调用onXXXChanged,这既产生了安全问题也不符合面向对象的初衷。
说到这里对于事件与委托的管理算是说明白了,那么平时常用的Action与Func,与委托又有什么关系呢?
二、Action 与Func
Action 委托:封装一个方法,该方法具有参数(0到16个参数)并且不返回值。
具体形式如下:https://msdn.microsoft.com/zh-cn/library/system.action(v=vs.110).aspx
Func<T, TResult> 委托:封装一个具有参数(0到16个参数)并返回 TResult 参数指定的类型值的方法。
具体形式如下:https://msdn.microsoft.com/zh-cn/library/bb534960(v=vs.110).aspx
那么这Action与Func是怎么实现的呢?
1、Action(以Action<T1, T2> 委托:封装一个方法,该方法具有两个参数并且不返回值为例)
从微软公布的源码中,可以看到,如下实现:
| 
 1 
 | 
public Action<bool,bool>  ac; | 
上面这个声明就是:该方法具有两个参数并且不返回值的委托。
其余使用方式与委托变量一样。
2、Func(以Func<T1, T2, TResult> 委托:封装一个具有两个参数并返回 TResult 参数指定的类型值的方法为例)
从微软公布的源码中,可以看到,如下实现:
此处,可以看出Func与Action是类似的,唯一的区别就是,Func必须指定返回值的类型,使用方式与委托咱们自己使用委托变量是一样的,直接使用相应参数的Func或者Action声明变量,=或者+=挂载函数(方法即可)
这两个其实说白了就是系统定义好的Delegate,他有很多重载的方法,便于各种应用情况下的调用。他在系统的System命名空间下,因此全局可见。
三、Predicate
 
是返回bool型的泛型委托,Predicate有且只有一个参数,返回值固定为bool。表示定义一组条件并确定指定对象是否符合这些条件的方法。此方法常在集合(Array 和 List<T>)的查找中被用到,如:数组,正则拼配的结果集中被用到。
官方文档:点击打开链接
具体用法demo如下:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
 | 
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;namespace IconTest{    public partial class Form2 : Form    {        Predicate<int> myPredicate;        int[] myNum = new int[8] { 12, 33, 89, 21, 15, 29, 40, 52 };        public int[] myResult;        public Form2()        {            InitializeComponent();            myPredicate = delegate(int curNum)                         {                if (curNum % 2 == 0)                {                    return true;                }                else                {                    return false;                }            };        }        private void Form2_Load(object sender, EventArgs e)        {            myResult = Array.FindAll(myNum, myPredicate);        }              }} | 
上例中说明了Predicate的使用,FindAll方法中,参数2即是一个Predicate,在具体的执行中,每一个数组的元素都会执行指定的方法,如果满足要求返回true,并会被存放在结果集中,不符合的则被剔除,最终返回的集合,即是结果判断后想要的集合。
Array.FindAll 泛型方法:点击打开链接
以上代码执行结果为:
那么Predicate<T>与委托又有什么关系呢?
从微软源码中可以看出Predicate<T>是返回bool型的泛型委托,从本质上来说与Func、Action、事件、委托变量并无本质区别。
参考文章:
- http://www.zhihu.com/question/28932542
 - 关于事件部分应用注意可以参考:http://www.cnblogs.com/buptzym/archive/2013/03/15/2962300.html
 - .NET Framework 源码:http://blog.csdn.net/jiankunking/article/details/44239613
 - Delegate 类:点击打开链接
 
通过IL分析C#中的委托、事件、Func、Action、Predicate之间的区别与联系的更多相关文章
- 浅谈C#中常见的委托<Func,Action,Predicate>(转)
		
一提到委托,浮现在我们脑海中的大概是听的最多的就是类似C++的函数指针吧,呵呵,至少我的第一个反应是这样的. 关于委托的定义和使用,已经有诸多的人讲解过,并且讲解细致入微,尤其是张子阳的那一篇.我就不 ...
 - Spring中Model、ModelMap及ModelAndView之间的区别
		
Spring中Model.ModelMap及ModelAndView之间的区别 1. Model(org.springframework.ui.Model)Model是一个接口,包含addAttr ...
 - C#中string.Empty、""和null 之间的区别
		
1.C#中string.Empty.""和null 之间的区别 (http://blog.csdn.net/henulwj/article/details/7830615)
 - js实例分析JavaScript中的事件委托和事件绑定
		
我们在学习JavaScript中,难免都会去网上查一些资料.也许偶尔就会遇到“事件委托”(也有的称我“事件代理”,这里不评论谁是谁非.以下全部称为“事件委托”),尤其是在查JavaScript的事件处 ...
 - mysql 中execute、executeQuery和executeUpdate之间的区别
		
在用纯JSP做一个页面报警功能的时候习惯性的用executeQuery来执行SQL语句,结果执行update时就遇到问题,语句能执行,但返回结果出现问题,另外还忽略了executeUpdate的返回值 ...
 - C++中指针和引用、数组之间的区别
		
指针指向一块内存,它的内容是所指内存的地址:而引用则是某块内存的别名,引用初始化后不能改变指向.使用时,引用更加安全,指针更加灵活. 初始化.引用必须初始化,且初始化之后不能呢改变:指针可以不必初始化 ...
 - Android中dip, dp, px,pt, sp之间的区别:
		
Android中dip.dp.sp.pt和px的区别 1.概述 过去,程序员通常以像素为单位设计计算机用户界面.例如:图片大小为80×32像素.这样处理的问题在于,如果在一个每英寸点数(dpi)更 ...
 - C#中的委托事件的分析
		
推荐:http://www.cnblogs.com/SkySoot/archive/2012/04/05/2433639.html 委托和事件在 .NET Framework 中的应用非常广泛,然而, ...
 - iOS中touches事件,addtarget ...action和GestureRecognizer详解
		
刚学完uiview,uicontrol类,许多人知道 touchesBegain,touchesMoved,touchesEnd,GestureRecognizer的用途,但仔细考虑这些事件之间的关系 ...
 
随机推荐
- Spring 和SpringMVC 的父子容器关系
			
Spring和SpringMVC作为Bean管理容器和MVC层的默认框架,已被众多WEB应用采用,而实际使用时,由于有了强大的注解功能,很多基于XML的配置方式已经被替代,但是在实际项目中,同时配 ...
 - Windows下Redis的安装使用
			
摘要 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted ...
 - 对弈类游戏的人工智能(5)--2048游戏AI的解读
			
前言: 闲得没事, 网上搜"游戏AI", 看到一篇<<2048游戏的最佳算法是?来看看AI版作者的回答>>的文章. 而这篇文章刚好和之前讲的对弈类游戏AI对 ...
 - SPARQL1.1 101 Language and Jena support
			
1 introduction definition cited from SPARQL 1.1 Overview: SPARQL 1.1 is a set of specifications that ...
 - 启动运行下载gradle速度太慢,手动添加
			
启动运行下载gradle速度太慢,并且容易卡死(感谢群友ˋ狠ㄨ得意提供支持)---国内网络访问地址 我们经常运行项目的时候会需要进行下载gradle,不过由于网络或者和谐的问题经常下载需要花很长时间或 ...
 - redis windows
			
下载 redis windows版本 redis-server redis.windows.conf //cmd 命令 启动成功 E:\redis\Redis-x64-3.0.500>red ...
 - jQuery-2.1.4.min.js:4 Uncaught TypeError: Illegal invocation
			
jQuery-2.1.4.min.js:4 Uncaught TypeError: Illegal invocation 此错误与crsf有关
 - iOS红马甲项目开发过程Bug总结(1)
			
在上线审核时,重新检测自己的app发现报错:"was compiled with optimization - steppingmay behave oddly; variables may ...
 - JavaBean基本用法示例(一)
			
一.首先创建一个类person,里面有四个成员:name,sex,age,info,添加四个成员所有的set和get方法. package com.kaly.bean; public class pe ...
 - [原创]cocos2d-x研习录-第二阶 概念类之布场层类(CCLayer)
			
上面说场景CCScene相当于一个大容器,那么布景层类CCLayer就是大容器里的若干个小容器.每个游戏场景CCScene会有很多层CCLayer,每一层CCLayer负责各自的任务.看一下CCLay ...