9、委托、事件、Lambda

关于委托,肯定是要有问题的。

第一个问题,委托用来干什么?

看.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表达式的过程。

时间: 2024-10-10 05:25:21

9、委托、事件、Lambda的相关文章

委托、Lambda表达式、事件系列07,使用EventHandler委托

谈到事件注册,EventHandler是最常用的. EventHandler是一个委托,接收2个形参.sender是指事件的发起者,e代表事件参数. □ 使用EventHandler实现猜拳游戏 使用EventHandler实现一个猜拳游戏,每次出拳,出剪刀.石头.布这三者的其中一种. 首先抽象出一个被观察者,其中提供了事件,提供了执行事件的方法. public class FistGame { public string FistName { get; set; } public event

委托、Lambda表达式、事件系列05,Action委托与闭包

来看使用Action委托的一个实例: static void Main(string[] args) { int i = 0; Action a = () => i++; a(); a(); Console.WriteLine(i); } 结果是期望能的2.但令人好奇的是:栈上的变量i是如何传递给Action委托的? 反编译进行查看,首先看Main方法对应的IL代码: 再看c_DisplayClass1的IL代码: 从中可以看出:→在托管堆上创建了一个名为c_DisplayClass1的实例→把

委托、Lambda表达式、事件系列04,委托链是怎样形成的, 多播委托

在"委托.Lambda表达式.事件系列01,委托是什么,委托的基本用法,委托的Method和Target属性"中,反编译委托,发现委托都是多播委托. 既然委托是多播委托,我们可以通过"+="把多个方法赋给委托变量,这样就形成了一个委托链, 它是怎样形成的?来看下面的例子: namespace ConsoleApplication3 { internal delegate void MySayDel(string msg); class Program { stati

委托、Lambda表达式、事件系列06,使用Action实现观察者模式

在"实现观察者模式(Observer Pattern)的2种方式"中,曾经通过接口的方式.委托与事件的方式实现过观察者模式.本篇体验使用Action实现此模式. 就举一个足球场上的例子,当裁判吹响终场哨,胜队庆祝,失败队落寞.把裁判看作是被观察者,比赛中的两队看作是观察者. 裁判作为被观察者需要提供一个Action委托供观察者方法注册. public class Referee { public Action DoSth; public void ISayGameOver() { Co

委托、Lambda表达式、事件系列03,从委托到Lamda表达式

在"委托.Lambda表达式.事件系列02,什么时候该用委托"一文中,使用委托让代码简洁了不少. namespace ConsoleApplication2 { internal delegate bool MyCalculateDelegate(int val); class Program { static void Main(string[] args) { IEnumerable<int> source = new List<int>(){2, 3, 4

委托、Lambda表达式、事件系列02,什么时候该用委托

假设要找出整型集合中小于5的数. static void Main(string[] args) { IEnumerable<int> source = new List<int>(){2, 3, 4, 5, 6, 7, 8, 9,10, 11}; var result = GetNumbersLessThanFive(source); foreach (int n in result) { Console.WriteLine(n); } } static IEnumerable&

C#委托事件

1.委托事件关键词:delegate 2.在声明函数的时候在函数类型后面加上 例:public delegate int Test(int x,int y) 3.定义委托变量:Test t1,委托接收的是方法引用. 4.委托变量可以当成函数的调用.用赋值“=” 委托变量 = 函数名 5.委托类型的变量,可以引用任何一个满足要求的方法,类似于C语言的函数指针. 6.一个委托变量可以使用“+=”挂接多个方法,也能使用“-=”动态地移除某个方法的引用. 7.引用多个方法的委托变量称为“多路委托”. 8

观察者(Observer)模式 * 委托事件

观察者(Observer)模式:定义了一种一对多的依赖关系.让多个观察者对象同时监听某一个主题对象.   这个主题对象发生变化时会通知所有观察者对象,使他们字段更新自己 /* * 抽象主题(Subject)角色:主题角色把所有对观察考对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者. 抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者(Observable)角色,一般用一个抽象类或者一个接口实现. * 抽象观察者(Observer)角色:为所有的具体观察者定义

[深入学习C#] 匿名函数、委托和Lambda表达式

匿名函数 匿名函数(Anonymous Function)是表示"内联"方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取决于转换的目标类型:如果是委托类型,则转换计算为引用匿名函数所定义的方法的委托:如果是表达式树类型,则转换将计算以对象结构形式表示方法结构的表达式树.  匿名函数有两种语法风格:Lambda表达式(lambda-expression)和匿名方法表达式(anonymous-method-ex

十二、C# 委托与Lambda表达式(匿名方法的另一种写法)

委托与Lambda表达式 1.委托概述 2.匿名方法 3.语句Lambda 4.表达式Lambda 5.表达式树 一.委托概述 相当于C++当中的方法指针,在C#中使用delegate 委托来提供相同的功能, 它将方法作为对象封装起来,允许在"运行时"间接地绑定一个方法调用. 声明的委托相当于一种自定义的数据类型. 1.背景 冒泡排序 1 static class SimpleSort1 2 { 3 public static void BubbleSort(int[] items)