Delegate,委托(或代理)是这样一种数据类型:它的变量可以引用到某一个符合要求的方法上,通过委托可以间接地调用该方法。
C#的委托类似于C语言的函数指针,区别在于C#的委托是面向对象的。
我们可以这样认为:在纯粹的面向对象语言C#中,方法也是一种特殊对象,对象的行为就是方法的行为,对象的属性是方法的返回值和参数列表。既然可以将方法认定为对象,那该对象也可以抽象出类来。这个类,就是Delegate类,即委托类。
例如,我们要为形如 void DoSomething(int a, double b) 的方法创建委托,则语法如下:
其中,DelegateHandler就是一个代理类型,可以认为它是一个“类”,是所有 返回类型为void,具备两个参数int a, double b的方法 的抽象,使用这个类型可以创建变量:
既然是变量,就可以给变量赋值,但这是委托,是一个方法的类,所以变量必须引用到一个方法上,例如如下方法:
有了这样一个方法,就可以用它对委托变量赋值了。
通过将一个函数的名称作为变量值赋值给委托变量,从而使用委托变量(handler)间接地可以调用Test对象t的TestDelegate方法。即,我们将对象t的TestDelegate委托给了DelegateHandler类型的变量handler来执行。
一个完整的代码例子:
通过上述的代码,我们可以看到,所谓的委托,就是一个可以保存方法的变量,这个非常类似于C语言中指向函数的指针,但C#的委托功能更为强大。
委托的最终目的是把一个类的某个方法传递到另一个类中去调用而无须传递前一个类的对象。即,一个类的对象可以运行另一个类的对象中的方法,但前者无须持有后者的引用。
在某些情况下,为了运行某个类对象的某个方法,但无论以类变量、超类变量或是接口变量传递这个对象都不合适,此时使用委托最为合理。
上面代码中表示的很明确,开关要能够控制灯和风扇,就必须能够访问灯对象的Open方法或风扇的Work方法,同一个开关对象可以控制灯或风扇对象,但灯和风扇既没有继承自相同的超类,也没有实现相同的接口,所以开关对象不可能去引用灯对象和风扇对象,但利用委托,则可以完美的解决该问题。
委托赋予了C#语言事件这一概念。
什么是事件?简而言之,就是一个对象的某个方法,必须在另一个对象的某个方法内部来调用。
例如电灯对象具有“亮”这个方法,但这个方法不会由灯对象自身来调用,而必须在开关对象的“打开开关”方法内来调用。也就是说,开关“打开开关”触发了灯“亮”这个事件。所以实现事件的方法很多,最简单的方法,可以让开关对象保存一个灯对象的一个引用,并在开关的“打开开关”方法内,通过这个引用调用灯的“亮”这个方法,从而实现事件触发,这种方法的缺点很明显:将特定的开关类型和特定的灯类型完全绑定在了一起,使得代码的可扩展性下降。这是编程的一个大忌:无继承关系的类和类之间必须是低耦合的(即没有继承关系的类和类之间尽量减少直接的联系)。高耦合表现为,当开关保存一个灯对象的引用后,意味着这个开关只能控制灯,触发灯的事件,而无法触发电风扇的事件。这和客观现实不符。
Java语言采用了接口的方法来实现事件,即定义一个具有Open方法的Openable接口,实现这个接口,在Open方法中打开灯或打开电风扇。开关对象保存Openable接口对象的引用。这种方法减少了类和类之间的耦合,是一种不错的方法。
C#引入了委托概念,就像前面代码中描述的一样,C#用一种专门的类型来处理事件触发,这一点比使用接口的Java在语义上更加明确,在使用上也更加方便。而且C#的委托实现了统一建模语言2.0版本中对于事件的描述,更符合软件工程的要求。
事件的多播
委托类型的变量引用到委托实现方法上,并利用属性来对外暴露委托(第21行和第31-38行)。这种方式没有任何问题,只是没有完全体现事件的特色。
在客观世界中,一个开关可以开启无数盏灯,或开启无数盏电风扇,或同时开启灯和风扇。即:事件应该具有同时出发多个对象行为的能力。这种能力称为事件的多播。
event关键字赋予一个委托类型多播的能力,使用event关键字修饰的委托变量,具有多播和触发事件的功能。
代码如下:
Fan.cs
Light.cs
Swticher.cs
Program.cs
开关类具备一个Click事件和一个OnClick方法(一般将在内部调用事件的方法称为OnXXX方法)。Click事件是一个使用event关键字修饰的委托类型,可以引用到所有返回类型为void,具有一个布尔类型参数的方法上,并通过其来调用方法。event关键字使得这个委托字段支持多播,可以使用+=运算符将多个符合委托的方法关联到该变量上。当OnClick在内部使用该委托变量时,所有关联在该委托变量上的方法都会被调用一次。
有了event修饰,就不用再为委托字段定义属性了,直接使用public修饰符将event修饰的委托类型字段公开即可。
使用事件的一般流程为:
1. 定义委托类型(例如EventHandler委托类型);
2. 使用event关键字修饰并声明委托类型字段(例如public event EventHandler Click);
3. 声明一个名为OnXXX的方法(例如OnClick)在方法内部触发委托定义的事件;
4. 在调用OnClick方法前,使用+=运算符为委托字段赋予合适的方法引用;
5. 调用OnXXX法,触发事件。