.NET进阶篇-语言章-2-Delegate委托、Event事件
知识只有经过整理才能形成技能
内容目录
一、概述
先说下委托,委托我们也经常用到。详尽了解委托是必要的,不然在异步多线程的编程中会一头雾水。委托本质就是一个类
,和我们平常定义的类没多大区别。只是这个类的作用的是描述一些方法,没有数据成员。一个委托定义了一类拥有同样返回类型和参数的方法规范
。委托的声明语法就是一个没有方法体的方法前面加上delegate
关键字。既然本质是一个类,那它就可以在任何可以定义普通类的位置来定义委托。委托是一个能把方法作为参数传递的对象
。
事件就简单了,事件就是委托的一个实例
。
二、解析委托知识点
1、委托本质
在vs中编码中,声明委托后,会发现委托的着色提示和类时一样。
但好像不是很有说服力。高级语法都做了很好的封装,方便编码人员。.net的二次编译,第一次编译成il中间语言,中间语言也是一种编程语言,只是它不像高级语言那么方便人类阅读。我们可以通过一些工具(像ilspy)反编译来窥探下它的内部逻辑。
如图中红框所示,我们定义的普通类mydelegate和委托类型noreturnpara(继承自multicastdelegate)是一致的,都是class。在委托类型noresultnopara中也有.ctor(在il中构造函数),此外还有我们以后会经常用到的invoke方法和begininvoke、endinvoke方法,前者是同步调用,后者是异步调用
。
2、委托的使用
我们使用委托一般就是三步走,第一步定义委托,第二部声明委托实例,第三部调用
。定义委托就像上面所示在一个没有方法体的方法前加上delegate关键字即可。它给定了一种约束,只能用规定的方法结构(返回值和参数)的实例化委托。
public delegate void noreturnnopara();//1 声明委托
noreturnnopara method = new noreturnnopara(this.donothing);//2 委托的实例化
method.invoke();//3 委托实例的调用
private void donothing()
{
console.writeline("this is donothing");
}
委托的实例实际就是代表你绑定的方法,把方法包装成一个变量,invoke时自动执行,这样用委托实例的调用就和直接方法调用效果一样
。那这样有什么用呢?用处太大了。这样意味着你可以把一个方法当做参数传递给另一个方法,这么说好像也体会不到。类比下我们泛型的用途,泛型用于设计和类型无关的对象功能
。那委托就是用于设计和方法无关的
,可以将方法行为(逻辑)在从外部注入到对象内部。
3、委托意义
逻辑解耦,减少重复代码
假如有以下集合list,student有身高、年龄属性。我们需要写一个方法查询出身高超过180cm的学生。(先不用list的find、where等方法,他们参数都是需要委托的,我们先考虑不用委托实现)。我们的方法可能像下面这样
然后如果需要查询出年龄大于20岁的呢,增加参数?方法重载?显然都要破坏原有类的封装。那我们想到“甩锅”,把变化的逻辑甩给调用者。上面两种查询也只是student.height>180和student.age>20判断逻辑的不同,可以把这部分当做参数传递进来。那么一组逻辑包装成变量,这就是委托。一个方法委托了我,你调用我就是调用那个方法
。改造后的方法像下面这样
这样我们只需要在外部调用的地方修改或增加相应的逻辑方法就可以。这里就相当于自己实现了list的where扩展方法,这也是它的内部原理。
但这样是不是也挺繁琐的,还要定义委托、定义个方法,然后实例化委托,好繁琐。既然委托规定了方法规范,那如果方法里不依赖的具体的类型,我们随意指定方法的参数类型该多好,还记得上节说的泛型吗。这里就使用泛型委托,我们也不自己去定义了,.net为我们封装了通用的两类泛型委托action<t>和func<t>,前者代表无返回值,后者代表有返回值
,每一类泛型委托都有十几个泛型参数可以指定,绝对够你用了。我们基本不用自己定义委托。这样还不够,我们还是要定义方法的呀。
代码封装,支持扩展
既然委托实例就是一个方法,结合泛型,那我们还可以做些更有趣的事情,下面实例代码的左右就是给一个方法增加了异常处理。(这只是无返回值的,你也可以加一个有返回值的)。这样就做到了,如果你的方法是指定给委托的,那么就可以捕获异常,你大可以不在具体方法内处理异常(实际,还是老老实实处理,这只是最后一道门)。我们甚至可以在调用任何方法前后加上日志,而不用修改原类的封装。有点类似aop(面向切片编程)的味道了。
匿名方法和lambda表达式
有时候简单的逻辑我们不必编写一个指定的方法。在实例化委托时可以直接指定一个匿名方法。像下面这样。优点当然是减少代码的复杂度了,还可以访问匿名方法外部的变量,但匿名方法内部不能使用break,continue等跳转语句。用来做简单的逻辑。
在c#3.0开始,我们有了lambda表达式代替匿名方法,它比匿名方法更加简单。lambda运算符“=>”(发音goesto)的左边列出了需要的参数,右边是利用该参数方法的实现代码
。如果表达式只有一个语句,还可以像图二那样简写。
异步多线程
异步多线程的实现都是基于委托的。开篇我们看到委托有begininvoke和endinvoke,这里先简单介绍下,我们会在后面异步多线程编程中详细解读。简单的代码如下图所示,结果会发现begininvoke实际是启用一个线程来调用方法的
。
begininvoke方法触发你的异步方法,它和你想要执行的异步方法有相同的参数。另外还有两个可选参数,第一个是asynccallback委托是异步完成的回调方法。第二个是用户自定义对象,该对象将传递到回调方法中。begininvoke立即返回并且不等待完成异步的调用(继续执行该下面的代码,不需要等待)。begininvoke返回iasyncresult接口,可用于检测异步调用的过程。通过endinvoke方法检测异步调用的结果。如果异步调用尚未完成,endinvoke将阻塞调用线程,直到它完成。
多播委托
前面使用的每个委托都只包含一个方法的调用。调用委托的次数和调用方法的次数相同。如果要调用多个方法,就需要多次显示调用这个委托(就像多次调用一个方法一毛一样)。委托也可以包含多个方法
。这种委托称为多播委托,但要注意如果方法有返回值,则只能得到委托调用最后一个方法的结果
。
使用运算符“+=”、“-=”来增加或去除委托的方法。+= 为委托实例按顺序增加方法,形成方法链,invoke时,按顺序依次执行。-= 为委托实例移除方法,从方法链的尾部开始匹配,遇到第一个完全吻合的,移除且只移除一个,没有也不异常
。但有一点要注意,多播委托时是不能使用异步的。
三、事件
事件也几乎无处不在,它提供一种发布/订阅机制
。我们做桌面开发,button类提供的click事件。触发click事件时调用的方法需要定义,其参数类型由委托类型定义。事件就是带event关键字的委托的实例
,event可以限制变量被外部调用/直接赋值。事件的标准用法如下,(当然你也可以自己定义更有意义的委托类型来代替action)。
事件就是委托的实例,所以事件能干的事情,普通的委托实例也都能干。只是为了某些场景下,事件规范了使用方法。如,事件只能用过+=来注册方法,只能在方法外部声明在内部调用,普通委托实例多用于回调,而事件多用于外部接口
。后面设计模式中的观察者模式就是其典型应用。
四、总结
其实感觉理的不是很细,主要还是知识点的扫盲巩固,再往细了去总结可能就会车轱辘话反反复复的了。
委托事件,我们平常会很常用的。特别是如果进阶一下,异步多线程编程时会用的更多,没有委托就没有异步多线程。委托就是描述一类方法的类型,委托的实例就是代表一个方法
。我们把一个方法当做一个委托的实例就可以进行传递,对于解耦有奇效。事件是委托的实例,最终是用来完成某一业务逻辑的一部分,只是这部分会变化,那么就把变化的形成封装出去,交给上层来指定,通过事件可以提供一个供外部扩展动作的接口
,这样就会更加的灵活。规定动作我内部写死,扩展的交给外部。
如果手机在手边,也可以关注下vx:xishaobb,互动或获取更多消息。当然这里也一直更新de。