开始
关于委托,肯定是要有问题的。
第一个问题,委托用来干什么?
看.net中的表述:在.net平台下,委托类型用来定义和相应应用程序中的回调。(回调?处理内存中两个实体双向通信的一种技术。)
第二个问题,委托和C++(其实起源于C语言)函数指针?
必然要说区别,虽然委托和函数指针都是指向以后要调用的方法。但委托相比于函数指针来说,主要有两点优势:一,委托是对象(我们知道,委托和类在一个层次)。二,委托是内置支持多路广播和异步方法调用的。
关于事件。
问题,事件用来干什么?
事件是为委托服务的,它使得我们处理委托类型的过程更简化和高效。
关于Lambda。
问题,这东西又是干什么的?
Lambda使用操作符=>来让我们调用委托,只不过更简单。
OK,看来,这都和委托密不可分,知道委托是个什么玩意,就能了解事件和Lambda是什么玩意了。
一、.net委托类型
之前的回调函数,C中的函数指针,也就是一个指向内存中一块地址完事。我们不了解更多的有关类型的信息,比如什么类型,什么返回值,妈蛋,都一抹黑。
委托出来了,觉得之前的那一套回调不安全,于是我变成类型安全的对象,指向程序中以后会被调用的方法(可以是多个。任性!)
1.委托能包含什么东西?有这些东西之后,有神马好处?
它所调用的函数方法名称;方法的参数(可选);方法的返回值(可选)。
有这些,在运行时我可以动态调用指向的方法。由此一来,本委托就自动有了同步或一部访问方法的能力,也就不用Thread那一套来调用辅助方法了,算是简化了编程工作。
2.委托干了什么,怎么干的?
定义:public delegate int BinaryOp(int x, int y);表示一个指向操作两个整数返回一个整数的方法。
当编译的时候,委托就会变成有三个公共方法的东东,三个方法:Invoke、BeginInvoke、EndInvoke方法,第一个用来同步调用的,后两个异步调用。而异步调用一般用来调用比较耗时的方法。(我们写界面的时候,经常会用到,防止界面卡逼,就异步调用,不影响界面的移动和其他的操作。)
在内存中她们的表示经常如下(其中下划线为红线的可以写成返回类型和参数类型(不论个数)):

由上面的那个伪代码就能看得出来,委托其实是和类在一个层次的东西,它还有基类,两个基类:System.MulticastDelegate和System.Delegate基类。
简单的委托实例(两个数的加法):

怎么干?委托在底层,实际上在MulticastDelegate派生类上调用了编译器生成的invoke方法。
说下委托的类型安全:如果试图将一个不匹配的方法传入委托,就会收到编译错误(例如上面的委托你传入一个参数的方法。)
3.委托怎么发对象通知?
定义一个Car类,他的公共成员如下:
定义委托,来判断,当加速的时候,会不会超过最大值而崩溃:
下面是加速方法,当速度不同的时候输出一些不同的信息:
在Program的Main方法里面每次加速 二十,并且记录每次加速之后的输出:
调用结果如下:

由上,可以总结出委托通知的步骤:
定义将通知发送给调用者的委托类型。
声明每个委托类型成员变量。
在泪殇创建辅助函数使调用者能指定由委托成员变量保存的方法。
修改方法,在适当的情形下调用委托中的调用列表。
多路广播是怎么回事?
意思就是说,一个委托对象可以维护一个可调用方法的列表,而不只是单独的一个方法。how?就涉及到+=和-=操作符了。
上面的委托调用方法再写一个:
Car中的方法修改:
调用代码:
结果:

同理,如果是想要移除方法,用-=操作符就可以了。相当于退订功能。
4.方法组转换语法
C#提供的方法组转换的语法,方便了委托操作,允许我们在调用委托作为参数的方法时候直接提供方法的名称,而不用创建委托对象。
如,上面的创建委托对象的过程都可以省了,成为如下代码:
c1.RegistWithCarEngine(OnCarEngineEvent2);
5.委托协变
拿上面的那个Car举例子来说,如果我们派生了一个NewCar的类,并创建一个委托类型,可以指向返回该类的方法。因为委托是安全类型,我们不能用返回Car累的委托,必须再定义一个新委托。
而委托协变,就是允许我们创建一个委托能指向返回类以及相关继承体系的方法。
我们可以将上面一个返回Car的委托结果,进行强制转换获得。
CarDelegate car = new carDelegate(new NewCar(xxx));用的时候,NewCar newCar = (NewCar)car;。
6.泛型委托
C#允许我们定义泛型委托类型,例如假设我们希望定义一个委托类型来调用任何返回void并且接受单个参数的方法。如果这个参数可能会不同,我们就可以通过类型参数来构建。

泛型委托提供了更灵活的方式来指定类型安全的形式进行调用的方法。
之前,我们一般通过使用System.Object参数来达到相似的目的:
public delegate void MyDelegate(object arg);
尽管如此,但是我们会因此失去类型安全并且可能还会有装箱损失。
二、C#事件
使用委托会遇到的问题。(破坏封装性)
我们没把委托定义为私有的情况下,调用者就可以直接访问委托对象,这样,调用者还可以重新把变量赋值给新的委托对象。Ooops!这下问题就出现了。
之前的方法相当于删了,并且调用者还可以直接调用委托的调用列表。
一句话说:公共成员打破了封装,不仅会导致代码难以维护和调试,还会导致应用程序的安全风险。
1.事件是干毛的?怎么干?
上面说了问题,事件就出来了,就可以知道,事件就是干这玩意的。(包装委托,使其不破坏封装性)。
怎么实现的呢?在编译器处理event关键字的时候,它会自动提供注册和注销方法以及任何必要的委托类型成员变量(貌似还简化了委托),并且这些委托成员变量总是私有的,因此不可能从触发事件的对象访问它们(ooh,安全了)。
事件的用法:
下面是修改之后的加速方法:

2.那么事件是如何封装委托的?(除了默认将委托置为private,还有下面的封装)
add_和remove_前缀。比如上面的AboutToBlow事件,在CIL代码中封装如下:

由此可以看来,事件只是节省了键入事件,下面说说如何在调用者这边监听传入的事件。
C#事件也简化了注册调用者事件处理程序的操作。现在无需指定自定义辅助方法,调用者仅需使用+=和-=操作符即可(操作符将在后台触发正确的add_xxx()方法或remove_xxx()方法)。
使用C#的事件注册语法修改Main方法如下:

当然,我们也可以用方法组转换语法。就不再陈述。
3.创建自定义的事件参数。
微软推荐的事件模式是什么样子的呢?
查看基础类库中某个类型发送的事件时,会发现底层委托的第一个参数是一个System.Object,第二个参数是一个派生自System.EventArgs的子类型。
System.EventArgs基类,表示一个不发送任何自定义信息的事件:
所以,如果我们如果要传递自定义数据,那么就需要创建一个派生自EventArgs的类。如下:

我们修改委托,增添一个CarEventArgs参数,就可以传递数据了。(事件不变。)
public delegate void CarEngineHandler(object sender, CarEventArgs args);//定义委托
调用方法:
if (Exploded != null)
{
Exploded(this,new CarEventArgs("Sorry, this car is dead..."));
}
如何接受者想与发送事件的对象交互,我们可以现实强制转换System.Object。这样就可以使用传递给事件通过对象中的任何公共成员。
三、C#匿名方法
匿名方法又是干毛的?
这可真是一波一波的,先是委托解决了函数指针的不安全性,但是后来嫌麻烦,又出现了事件。结果,事件也看起来啰里啰嗦,好吧,匿名方法登场:
上面的注册事件处理程序,被改成了:
c1.AboutToBlow += delegate (object sender,CarEventArgs e)
{
Console.WriteLine("Eek! Going to fast!");
};
看起来似乎是处理和注册声明到一块儿了。
说明:匿名方法中最后一个大括号必须以分好结束,否则,将产生一个编译错误。
下面是匿名方法的语法的伪代码:
匿名方法非常有趣,它使我们能访问定义他们的方法的本地变量。这些变量成为匿名方法的外部变量。有关匿名方法和定义方法的作用域之间的交互,有几个重要的知识点:
匿名方法不能访问定义方法中的ref或out参数。
匿名方法中的本地变量不能与外部方法中的本地变量重名。
匿名方法可以访问外部类作用域中的实例变量(或静态变量)。
四、Lambda表达式
Lambda表达式是干毛用的?好吧,彻底被干败了,觉得匿名方法还麻烦,都把裤子脱了还嫌麻烦,于是就让你比脱了裤子还简单,看我Lambda的吧:
编写:Lambda表达式是这样编写的,首先,定义一个参数列表,“=>”标记紧随其后,然后就是处理这些参数的语句。下面表达式可以说明:
ArgumentsToProcess=>StatementsToProcessThem。
那么用Lambda表达式,如何表达上面的注册处理程序?如下:
c1.AboutToBlow +=(sender,e)=>{ Console.WriteLine("Eek! Going to fast!");//在里面可以处理传入的参数 };
五、小结
这主要讨论了四个东西,其实就是一个,那就是委托,然后优化成事件。然后简化成匿名方法,最后简化成Lambda表达式的过程。
- C# Note2:委托(delegate) & Lambda表达式 & 事件(event)
前言 本文主要讲述委托和Lambda表达式的基础知识,以及如何通过Lambda表达式实现委托调用,并阐述.NET如何将委托用作实现事件的方式. 参考:C#高级编程 1.什么是委托(delegate)? ...
- C# 委托、lambda表达式和事件
什么是委托?委托就是持有一个或多个方法的对象,并且该对象可以执行,可以传递. using System; using System.Collections.Generic; using System. ...
- 委托、Lambda表达式、事件系列07,使用EventHandler委托
谈到事件注册,EventHandler是最常用的. EventHandler是一个委托,接收2个形参.sender是指事件的发起者,e代表事件参数. □ 使用EventHandler实现猜拳游戏 使用 ...
- 委托、Lambda表达式、事件系列06,使用Action实现观察者模式,体验委托和事件的区别
在"实现观察者模式(Observer Pattern)的2种方式"中,曾经通过接口的方式.委托与事件的方式实现过观察者模式.本篇体验使用Action实现此模式,并从中体验委托与事件 ...
- 委托、Lambda表达式、事件系列05,Action委托与闭包
来看使用Action委托的一个实例: static void Main(string[] args) { int i = 0; Action a = () => i++; a(); a(); C ...
- 委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托, 调用委托链方法,委托链异常处理
委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链.本篇的话题包括:委托链是怎样形成的,如何调用委托链方法,以及委托链异常处理. □ 调用返回类型为 ...
- 委托、Lambda表达式、事件系列03,从委托到Lamda表达式
在"委托.Lambda表达式.事件系列02,什么时候该用委托"一文中,使用委托让代码简洁了不少. namespace ConsoleApplication2 { internal ...
- 委托、Lambda表达式、事件系列02,什么时候该用委托
假设要找出整型集合中小于5的数. static void Main(string[] args) { IEnumerable<int> source = new List<int&g ...
- 委托、Lambda表达式、事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性
委托是一个类. namespace ConsoleApplication1 { internal delegate void MyDelegate(int val); class Program { ...
- 委托学习过程及委托、Lambda表达式和匿名方法的关系总结及事件总结
第一章,当开始学习委托的时候,我们会问什么是委托?为什么要学习委托? 一,什么是委托? 委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法, ...
随机推荐
- 【.net 深呼吸】细说CodeDom(1):结构大观
CodeDom 是啥东东?Html Dom听过吧,XML Dom听过吧.DOM一般可翻译为 文档对象模型,那 Code + DOM呢,自然是指代码文档模型了.如果你从来没接触过 CodeDom,你大概 ...
- react-redux
1. 首先redux,与react是两个独立的个体,项目中可以只用react,也可以只用redux 1.1 react-redux: 是一个redux作者专门为react制作的 redux, 增加了新 ...
- Java中Comparable与Comparator的区别
相同 Comparable和Comparator都是用来实现对象的比较.排序 要想对象比较.排序,都需要实现Comparable或Comparator接口 Comparable和Comparator都 ...
- NLP点滴——文本相似度
[TOC] 前言 在自然语言处理过程中,经常会涉及到如何度量两个文本之间的相似性,我们都知道文本是一种高维的语义空间,如何对其进行抽象分解,从而能够站在数学角度去量化其相似性.而有了文本之间相似性的度 ...
- 数据的双向绑定 Angular JS
接触AngularJS许了,时常问自己一些问题,如果是我实现它,会在哪些方面选择跟它相同的道路,哪些方面不同.为此,记录了一些思考,给自己回顾,也供他人参考. 初步大致有以下几个方面: 数据双向绑定 ...
- [原]Cachedb 网络模块文档
Cachedb 网络模块文档 整体结构 多路复用 (epoll 模块) 事件驱动 (事件封装) 缓冲管理 (上层buffer管理) 设计思想 层次化的设计,每一个模块只调用上一个模块的接口,并将耦合聚 ...
- DDD领域驱动设计 - 设计文档模板
设计文档模板: 系统背景和定位 业务需求描述 系统用例图 关键业务流程图 领域语言整理,主要是整理领域中的各种术语的定义,名词解释 领域划分(分析出子域.核心域.支撑域) 每个子域的领域模型设计(实体 ...
- BPM配置故事之案例9-根据表单数据调整审批线路2
老李:好久不见啊,小明. 小明:-- 老李:不少部门有物资着急使用,现在的审批流程太慢了,申请时增加一个是否加急的选项吧.如果选加急,金额1000以下的直接到我这里,我审批完就通过,超过1000的直接 ...
- 修改MySQL默认字符集编码
好记心不如烂笔头,很多东西当时没记下来,过了就忘了,下次用到时又得浪费好多时间才能解决.今天又遇到修改MySQL默认字符集编码的问题,折腾了半天解决了,赶快记录下来,以后就不用每次折腾了. 查看MyS ...
- 分享一款自己改进的皮肤“verdant”.
- -!我总觉得我不应该这个样子了,这是个不好的习惯,面对博客,我每周或者每个月都会有审美疲劳,然后又写一个皮肤模板,不停的循环,至今都写了好多好多了,都记不清了,汗... 下面是我这今天审美疲劳写的 ...