你的位置:首页 > ASP.net教程

[ASP.net教程]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表达式的过程。