委托和事件(C#)
基于事件驱动的程序设计是目前主流的程序设计方法,它是 Windows 应用程序设计和 Web 应用程序的基础。
完整的事件处理系统必须包含以下三大组成要素:
(1)事件源:指能触发事件的对象,有时又称为事件的发送者或事件的发布者。
(2)侦听器:指能接收到事件事件消息的对象,Windows 提供了基础的事件侦听服务。
(3)事件处理程序:在事件发生时能对事件进行有效处理,又称事件方法或事件函数。包含事件处理程序的对象称为事件的接受者,有时又称事件的订阅者。
为了确保事件处理程序被执行,在程序设计时必须预先将一个事件处理与事件源对象联系起来,这个操作称为事件的绑定。C# 通过委托来绑定事件。
委托
委托(delegate)是一种动态调用方法的类型,它与类、接口和数组相同,属于引用类型。
委托的声明
委托是一种引用型的数据类型,在 C# 中使用关键字 delegate 声明委托,一般形式如下:
[访问修饰符] delegate 返回值类型 委托名 ([参数列表])
例:
public delegate int Calculate(int x,int y);
表示声明了一个名为 Calculate 的委托,可以用来引用任何具有两个 int 型的参数且返回值也是 int 型的方法。
其中,访问修饰符与声明类、接口和结构的访问修饰符相同,返回值类型是指将要动态调用的方法的返回值类型,参数列表是将要动态调用的方法的形参列表,当方法无参数时,省略参数列表。
委托实例化
因为委托是一种特殊的数据类型,因此必须实例化之后才能用来调用方法。实例化委托的一般形式如下:
委托类型 委托变量名 = new 委托型构造函数(委托要引用的方法名)
其中,委托类型必须事先使用 delegate 声明。
例如:
// 定义两个方法
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
// 实例化委托
Calculate a = new Calculate(Mul);
Calculate b = new Calculate(Div);
其中,a 和 b 为委托的对象。
由于实例化委托实际上是创建了一个对象,所以委托对象可以参与赋值运算,甚至作为方法参数进行传递。
使用委托
在实例化委托之后,就可以通过委托对象调用它所引用的方法。在使用委托对象调用所引用的方法时,必须保证参数的类型、个数、顺序和方法声明匹配。
例:
Calculate calc = new Calculate(Mul);
int result = calc(3,7);
就表示通过 Calculate 型的委托对象 calc 来调用方法 Mul ,实参为 3 和 7,因此最终返回并赋值给变量 result 的值为 21。
多路广播与委托的组合
C# 的所有委托都是隐式的多路广播委托。向一个委托的调用列表添加多个方法引用,可通过该委托一次性调用所有的方法,这一过程称为多路广播。
实现多路广播的方法有以下两种:
(1)通过“+”运算符直接将两个同类型的委托对象组合起来。
例:
Calculate a = new Calculate(Mul);
Calculate b = new Calculate(Div);
a = a + b;
这样委托对象 a 就可以同时调用 Mul 和 Div 了。
(2)通过“+=”运算符将新创建的委托对象添加到委托调用列表中。另外,还可以使用“-=”运算符来移除调用列表中的委托对象。
例:
Calculate a = new Calculate(Mul);
a += new Calculate(Div);
这样,Mul 和 Div 方法都列入了委托对象 a 的调用列表。
【注意】:由于一个委托对象只能返回一个值且返回调用列表中最后一个方法的返回值,因此为避免混淆,建议在使用多路广播时每个方法均用 void 定义。
事件
触发事件的对象称为发布者,提供事件处理程序的对象称为订阅者,基于事件驱动模型的程序使用委托来绑定事件和事件方法。C# 允许使用标准的 EventHandler 委托来声明标准事件,也允许先自定义委托,再声明自定义事件。
事件的声明
EventHandler 是一个预定义的委托,它定义了一个无返回值的方法。其格式如下:
public delegate void EventHandler(Object sender,EventArgs e);
其中,第一个参数 sender,类型为 Object ,表示事件发布者本身。第二个参数 e,用来传递事件的相关数据信息,数据类型为 EventArgs 及其派生类。
实际上,标准的 EventArgs 并不包含任何事件数据,因此 EventHandler 专用于表示不生成数据的事件方法。如果事件要生成数据,则必须提供自定义事件数据类型,该类型从 EventArgs 派生,提供保存事件数据所需的全部字段或属性,这样发布者可以将待定的数据发送给接受者。
用标准的 EventHandler 委托可声明不包含数据的标准事件,一般形式如下:
public event EventHandler 事件名;
其中,事件名通常使用 on 作为前缀符。
例:
public event EventHandler onClick;
就表示定义了一个名为 onClick 的事件。
要想生成包含数据的事件,必须先自定义事件数据类型,然后再声明事件。具体实现方法有以下两种:
(1)先自定义委托,再定义事件,一般形式如下:
public class 事件数据类型: EventArgs {//封装数据信息};
public delegate 返回值类型 委托类型名 (Object sender, 事件数据类型 e);
public event 委托类型名 事件名;
(2)使用泛型 EventHandler 定义事件,一般形式如下:
public class 事件数据类型: EventArgs {//封装数据信息}
public event EventHandler <事件数据类型>事件名
订阅事件
声明事件的实质只是定义了一个委托型的变量,并不意味这就能够成功触发事件,还需要完成如下工作:
- 在事件的接受者中定义一个方法来响应这个事件;
- 通过创建委托对象把事件与事件方法联系起来(又称绑定事件,或订阅事件);
负责绑定事件与事件方法的类就称为事件的订阅者。
订阅事件的一般形式如下:
事件名 += new 事件委托类名 (事件方法);
例,想要对温度的变化情况进行预警,可先创建一个 tw_OnWarning 方法,该方法根据温度高低,进行预警,然后把方法和事件 OnWarning 绑定起来即可。这样,当温度预警事件触发时,该方法将被自动调用。绑定 OnWarning 事件代码如下:
TemperatureWarning tw = new TemperatureWarning ();
tw.OnWarning += new TemperatureWarning.TemperatureHandler (tw.OnWarning); //订阅事件
如果使用泛型 EventHandler 定义的事件,则使用如下代码:
tw.OnWarning += new EventHandler<TemperatureEventArgs>(tw.OnWarning); //订阅事件
其中,“+=”运算符把新创建的引用 tw.OnWarning 方法的委托对象与 OnWarning 事件绑定起来,也就完成了 TemperatureWarning 类的 OnWarning 事件的订阅操作。
事件触发时,调用的 tw.OnWarning 方法签名如下:
private void tw.OnWarning (object sender, TemperatureEventArgs e);
【注意】:在订阅事件时要注意以下几点:
- 订阅事件的操作由事件接受者类实现。
- 每个事件可有多个处理程序,多个处理程序按顺序调用。如果一个处理程序引发异常,还未调用的处理程序则没有机会接收事件。为此,建议事件处理程序迅速处理事件并避免引发异常。
- 订阅事件时,必须创建一个与事件具有相同类型的委托对象,把事件方法作为委托目标,使用 += 运算符把事件方法添加到源对象的事件之中。
- 若要取消订阅事件,可以使用 -= 运算符从源对象的事件中移除事件方法的委托。
触发事件
在完成事件的声明和订阅之后,就可以引用事件了。引用事件又称触发事件或点火,而负责触发事件的类就称为事件的发布者。C# 程序中,触发事件和委托调用相同,但要注意使用匹配的事件参数。事件一旦触发,即将调用相应的事件方法。