文档章节

庖丁解牛——深入解析委托和事件

 木宛城主
发布于 2015/03/02 19:42
字数 2289
阅读 5
收藏 0
点赞 0
评论 0

这篇博文我不讲委托和事件的概念,因为大段的文字概念没有任何意义。

具体想了解,委托和事件的概念可以MSDN查阅。

我这篇文章的主题思路是委托如何一步步进化成事件:

何为委托--->委托来实现事件--->用方法对委托的封装--->Event的

add,remove方法的引入--->标准事件写法--->反编译探究竟。

用几个例子以及Reflector反编译探究委托和事件的关系。不足之处,还望多多指教... 

何为委托:

首先,委托是一种类型,是一种定义了方法签名的类型。

委托可以理解为函数指针(安全),并且委托约束了方法的签名(由返回类型和参数组成),

所以实例化委托时,可以将其实例与任何具有相同签名(由返回类型和参数组成)得方法相关联,

如果按照C语言函数指针理解,即委托实例指向某个方法。

为什么要用委托:

举个简单的例子:

例如,需要判断一个数是为奇数还是偶数?

可能我们会这样实现:

     static void Main(string[] args) { Console.WriteLine((4 % 2 == 0) ? "偶数" : "奇数"); Console.WriteLine((5 % 2 == 0) ? "偶数" : "奇数"); Console.ReadKey(); }

上面例子很简单,但是很不灵活,我们稍加改进:

static void Main(string[] args) { Console.WriteLine("请输入一个数字:"); int i = int.Parse(Console.ReadLine()); Console.WriteLine((i%2==0)?"偶数":"奇数"); Console.ReadKey(); }

上面这个简单的例子,也是挺有玄机的。对于程序员,我们不关心客户端用户传过来是奇数还是偶数,况且我们也不知道传过来的参数到底是多少,

我们只关心怎样来实现功能。对于用户来说,他们不必关心底层到底是怎样实现功能的,他们只负责输入数字即可“坐享其成”。这个例子,

可以理解成一个最简单的解耦。

 

 

  看了上面这个例子,我们再举一个例子来演示委托怎么替做什么:

  

    //委托是一种定义方法签名的类型。 当实例化委托时,可以将其实例与任何具有兼容签名的方法相关联。 可以通过委托实例调用方法。 //一个委托声明:
    public delegate void ChangeDelegate(int i); class Program { static void Main(string[] args) { ChangeDelegate c = null; if (DateTime.Now.Second%2==0) { c = Even;//委托可以理解为函数指针(安全),并且委托类型ChangeDelegate约束了参数类型,所以c可以指向Even方法
 c(DateTime.Now.Second); } else { //c = Odd;
                c = new ChangeDelegate(Odd);//标准写法
 c(DateTime.Now.Second); } Console.ReadKey(); } static void Even(int i) { Console.WriteLine("{0}是偶数",i); } static void Odd(int i) { Console.WriteLine("{0}是奇数",i); } }

上面代码可以看出,程序员并不知道委托实例到底指向那个函数,但他可以确定,指向的那个方法必定是受ChangeDelegate约束的。

再看一个例子,我们要对数组进行操作:

 class Program { static void Main(string[] args) { int[] arr = { 1,2,3,4,5,6,7,8,9}; List<int> newList=Filter(arr,IsOdd); Console.WriteLine(string.Join("-",newList.ToArray())); Console.ReadKey(); } /// <summary>
        /// 判断是否为偶数 /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        static bool IsEven(int i) { return i%2==0; } /// <summary>
        /// 判断是否为奇数 /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        static bool IsOdd(int i) { return i%2==1; } static List<int> Filter(int[] value, FilterDelegate f) { List<int> list=new List<int> (); foreach (var item in value) { if (f(item)) { list.Add(item); } } return list; } } delegate bool FilterDelegate(int i);

 

 所以,static List<int> Filter(int[] value, FilterDelegate f),这儿只需要传过来一个方法,

而我们程序员并不关心这是个什么方法,能替我们做什么,这就是委托的好处。

 

 

 

  事件和委托的联系:学习事件之前,先来用委托来模拟实现事件:

 

    class Program { static void Main(string[] args) { Counter c = new Counter(); c.onCount = Count;//相当于订阅了一个事件
            c.onCount += Count_2;//可以多个人同时监听了
            int j = 0; while (j<=100) { j++; c.Next(); } Console.ReadKey(); } static void Count(int value) { Console.WriteLine("Count监听了{0}是偶数", value); } static void Count_2(int value) { Console.WriteLine("Count_2监听了{0}是偶数", value); } } class Counter { public OnCountDelegate onCount;//把委托对象声明为一个字段
        private int i = 0; public void Next() { i++; if (i%2==0) { if (onCount!=null) { //解耦:解除耦合。 //不用关心到底指向谁,调用就行
                    onCount(i);//触发事件,调用onCount指向的函数,相当于把事件通知出去
 } } } } public delegate void OnCountDelegate(int value);
  c.onCount = Count;
  c.onCount += Count_2;
 相当于监听事件,触发事件时,只要哦调用onCount指向的函数,这样相当于把事件通知出去。所以上面这个Demo之后,我们再可以对委托来实现事件进行扩展:
 我们自定义一个双击火箭手按钮[用户控件]:
 
namespace 双击火箭手按钮 { //声明一个委托
    public delegate void DoubleClickDelegate(); public partial class DoubleClickButton : UserControl { public DoubleClickButton() { InitializeComponent(); } private int i; //把一个的委托对象定义为Public字段
        public DoubleClickDelegate OnDoubleClick; private void button1_Click(object sender, EventArgs e) { i++; if (i==2) { i = 0; if (OnDoubleClick!=null) { OnDoubleClick();//调用事件的响应函数
 } } } } }

然后我们在Form1中托入一个我们自定义的DoubleClickButton:

这儿我们可以仿照普通Button按钮监听Click事件:  this.button1.Click += new System.EventHandler(this.button1_Click);来那样做:对DoubleClickButton的

OnDoubleClick事件进行监听:
        private void Form1_Load(object sender, EventArgs e) { doubleClickButton1.OnDoubleClick = doFire; doubleClickButton1.OnDoubleClick += doIce; } void doFire() { MessageBox.Show("双击火枪手开火"); } void doIce() { MessageBox.Show("双击火枪手下冰雨"); }

 

这样一个简单的用户控件就完成了,双击两下触发了OnDoubleClick事件,并且去执行相关联的响应函数(doFire,doIce)。

接着我们在对这个程序进行修改,来干扰我们的双击火枪手按钮:

我们在捣乱这个按钮的中写入:

        private void button1_Click(object sender, EventArgs e) { doubleClickButton1.OnDoubleClick = null; }
上面的操作意味着我们把委托变量指向了NULL,这就破坏了之前我们的监听事件。
再比如:在模拟执行这个按钮中写入:
        private void button2_Click(object sender, EventArgs e) { doubleClickButton1.OnDoubleClick(); }

上面代码模拟执行了双击火枪后按钮,本来需要双击两下才能触发事件,而这儿可以直接去执行事件的响应函数。

 

所以为了防止外界对我的事件的干扰,我们把

public OnCountDelegate onCount;//把委托对象声明为一个字段

改为:

   //把委托申明为Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
   private DoubleClickDelegate OnDoubleClick;

再对私有的委托用一个AddDoubleClick进行对外界的过滤,所以完整代码应该是这样的:

 //把委托申明为Private,防止外界直接=NULL或者OnDoubleClick()仿照事件
        private DoubleClickDelegate _OnDoubleClick; public void AddDoubleClick(DoubleClickDelegate d) { _OnDoubleClick += d; } private void button1_Click(object sender, EventArgs e) { i++; if (i==2) { i = 0; if (_OnDoubleClick!=null) { _OnDoubleClick();//调用事件的响应函数
 } } }

上面这样处理之后,我们的控件就不会被外界干扰了。

但是这样操作太复杂,所以微软为我们提供了更为简便的方式既 Event:

对上面的双击火枪手按钮在做稍微修改:

namespace 三击暴走按钮 { public delegate void RampageDelegate(); public partial class RampageThreeClickButton : UserControl { //定义一个私有的委托类型字段
        private RampageDelegate onRampage; //类似于属性那样对委托进行了封装
        public event RampageDelegate OnRampage { add { onRampage += value; } remove { onRampage -= value; } } private int count; public RampageThreeClickButton() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { count++; if (count==3) { if (onRampage!=null) { onRampage(); } count = 0; } } } }

再将定义好的暴走控件拖到Form1中:

后台代码是这样的:

       public Form1() { InitializeComponent(); } private void rampageThreeClickButton1_Load(object sender, EventArgs e) { //对事件进行注册,即监听事件,只能用+=,不用用=,这样防止了外界=NULL干扰
            rampageThreeClickButton1.OnRampage += doFire; //同理
            rampageThreeClickButton1.OnRampage += new RampageDelegate(doThunder); rampageThreeClickButton1.OnRampage += new RampageDelegate(doIce); } void doFire() { MessageBox.Show("烈焰之怒"); } void doThunder() { MessageBox.Show("上古雷霆"); } void doIce() { MessageBox.Show("极地冰雨"); }

现在还能干扰吗?当然不行:

 

 

 

OK,到目前为止,大家对委托和事件肯定有一个深刻的认识:

我们对事件进行反编译之后,相信一切疑问皆是浮云:

我们举一个标准的事件案例:

namespace DoubleKissinUButton { public delegate void KissinUDelegate();//委托别忘记
    public partial class KissinUButton : UserControl { public event KissinUDelegate onKissinU;//用Event关键字来申明一个事件
        public KissinUButton() { InitializeComponent(); } private int i = 0; private void button1_Click(object sender, EventArgs e) { i++; if (i==2) { if (onKissinU!=null) { onKissinU(); } } } } }

界面:

 public partial class Form1 : Form { public Form1() { InitializeComponent(); kissinUButton1.onKissinU += new KissinUDelegate(kissinUButton1_onKissinU); } void kissinUButton1_onKissinU() { MessageBox.Show("点我我就吻吻你"); } private void Form1_Load(object sender, EventArgs e) { } }

通过反编译看看:

 

 

点击进入看看:

 

委托和事件没有可比性,因为委托是类型,事件是对象,上面说的是委托的对象(用委托方式实现的事件)和(标准的event方式实现)事件的区别。事件的内部是用委托实现的。 因为对于事件来讲,外部只能“注册自己+=、注销自己-=”,外界不可以注销其他的注册者,外界不可以主动触发事件,因此如果用Delegate就没法进行上面的控制,因此诞生了事件这种语法。add、remove。

事件是用来阉割委托实例的。事件只能add、remove自己,不能赋值。事件只能+=、-=,不能=、不能外部触发事件。

© 著作权归作者所有

共有 人打赏支持
粉丝 2
博文 222
码字总数 199010
作品 0
黄浦
WPF 基础到企业应用系列索引

WPF 基础到企业应用系列索引 WPF 基础到企业应用系列从WPF的起源、背景、历史、现状讲起,然后逐渐过渡到基础、企业应用以及经验总结等。整个系列计划三十篇左右,每一篇都会认真完成,怀着宁...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

WPF 基础到企业应用系列索引

WPF 基础到企业应用系列索引 WPF 基础到企业应用系列从WPF的起源、背景、历史、现状讲起,然后逐渐过渡到基础、企业应用以及经验总结等。整个系列计划三十篇左右,每一篇都会认真完成,怀着宁...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

开发微信小程序的必备技能图谱

今天被微信小程序彻底刷屏了,哎呀,JS开发者坐等涨工资吧。 小程序是一种不需要下载安装即可使用的应用,它实现了应用“触手可及”的梦想,用户扫一扫或者搜一下即可打开应用。也体现了“用...

Yomut ⋅ 2016/09/23 ⋅ 0

《深入探索Netty原理及源码分析》文集小结

写在2017年末尾,翻看文集的第一篇文章已经是三个月前的事了,也没想过这文集会写那么久,这么慢。。。 Netty文集中的文章主要都是我学习过程的笔记,写博客的主要目的是为了通过输出来倒逼输...

tomas家的小拨浪鼓 ⋅ 2017/12/30 ⋅ 0

编写高质量代码改善C#程序的157个建议[为泛型指定初始值、使用委托声明、使用Lambda替代方法和匿名方法]

前言   泛型并不是C#语言一开始就带有的特性,而是在FCL2.0之后实现的新功能。基于泛型,我们得以将类型参数化,以便更大范围地进行代码复用。同时,它减少了泛型类及泛型方法中的转型,确...

aehyok ⋅ 2014/05/15 ⋅ 0

C#入门:委托的简单理解

C#入门:委托的详细理解 C#入门:委托的简单理解 我们习惯于把数据作为参数传递给方法,例如:int max(int a,int b)。而有时某个方法执行的操作并不是针对数据进行的,而是要对另一个方法进行操...

掬一捧 ⋅ 2012/08/27 ⋅ 0

WPF 基础到企业应用系列5——WPF千年轮回 续前缘

一,摘要 首先很高兴这个系列能得到大家的关注和支持,前端时间身体状况不适,所以暂停了更新,对此表示非常抱歉,以后会逐渐加快进度,不过由于这是一个很长的系列,我也想把它写好,所以以...

晨曦之光 ⋅ 2012/03/09 ⋅ 0

iOS 委托代理(delegate)入门

一、什么是委托 刚接触iOS开发的朋友肯定对委托这个东西感觉到陌生和迷惑,特别是Java转过来的(博主中枪),那么今天我们就简单的谈谈委托这个东西,atany会尽量做到说的浅显易懂的~ 首先让...

wakice ⋅ 2017/10/07 ⋅ 0

Controller接口控制器4

本文为转载学习 原文链接:http://jinnianshilongnian.iteye.com/blog/1630585 MultiActionController 之前学过的控制器如AbstractCommandController、SimpleFormController等一般对应一个功...

heroShane ⋅ 2014/01/25 ⋅ 1

为什么委托的减法(- 或 -=)可能出现非预期的结果?(Delegate Subtraction Has Unpredictable Result)

当我们为一个委托写 的时候,ReSharper 会提示“Delegate Subtraction Has Unpredictable Result”,即“委托的减法可能出现非预期的结果”。然而在写为事件写 的时候却并没有这样的提示。然...

WPwalter ⋅ 2017/12/30 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

SAS笔记-宏2

宏是一种文本,一般来说其编译是在程序执行之前。 宏变量的创建 %let语句 %let macro_variables = text; %let是常见的宏变量建立方式,其编译就在执行前。如下例中,想要宏变量test等于数据集...

tonorth123 ⋅ 9分钟前 ⋅ 0

如何使用serverchan微信推送告警

之前实现推送告警信息到微信的方法有如下几种: 1、通过企业公众号实现----收费: 2、通过QQ邮箱,在微信平台上开启收到邮件进行提醒; 3、第三方告警平台API,一般也是收费的; 不过最近看文...

问题终结者 ⋅ 35分钟前 ⋅ 0

TCP的RPC

RPC就是远程方法调用(Remote Process Call ),包含了客户端和服务端,涉及了对象的序列化传输。 1.服务端启动,注册远程调用的类2.客户端发送请求信息包含类、方法、参数的一些信息、序列化传...

Cobbage ⋅ 56分钟前 ⋅ 0

IOS-UI UI初步代码布局添加事件

ISO开发界面,UI是必须学习的一部分,其实很早之前想学来了,一直没有沉下心来学习。看到IOS的代码风格和布局就别扭的不行,跟java代码和android布局比较显得不是那么方便,所以一直到现在。...

京一 ⋅ 今天 ⋅ 0

浅谈OpenDaylight的二次开发

OpenDaylight作为一款开源SDN网络控制器,依托于强大的社区支持以及功能特性,成为了目前主流的SDN网络控制器开发平台。在比较稳定的OpenDaylight Helium版本中,已经为开发者提供了大量的网...

wangxuwei ⋅ 今天 ⋅ 0

API 开发中可选择传递 token 接口遇到的一个坑

在做 API 开发时,不可避免会涉及到登录验证,我使用的是jwt-auth 在登录中会经常遇到一个token过期的问题,在config/jwt.php默认设置中,这个过期时间是一个小时,不过为了安全也可以设置更...

等月人 ⋅ 今天 ⋅ 0

Java NIO之文件处理

程序要操作本地操作系统的一个文件,可以分为以下三个部分: 对文件位置的操作 对文件的操作 对文件内容的操作 其中,对文件内容的操作在 Java NIO之Channel 中已经有了介绍,通过FileChann...

士别三日 ⋅ 今天 ⋅ 0

Maven的pom.xml配置文件详解

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.......

小海bug ⋅ 今天 ⋅ 0

解决httpclient超时设置不生效的问题

最近公司有项目需要通过http调用第三方服务,且第三方服务偶有超时,故需要设置一定的超时时间防止不响应的情况出现。 初始设置如下: [java] view plain copy //超时设置 RequestConfig re...

Mr_Tea伯奕 ⋅ 今天 ⋅ 0

过滤器Filter和拦截器HandlerInterceptor

过滤器 依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要...

hutaishi ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部