事件是一种特殊类型的委托。
1 事件基础
当某个类或是对象想把自身的状况通知给另外一个类或是对象时,而类或对象在接收到通知(以下便使用术语事件)时,可能需要做出某些处理,但在定义发出事件的类时,一般是不知道接收方应当做何种处理,而事件的出现能很好的解决这个问题。
发送(或引发)事件的类称为“发行者”,接收(或处理)事件的类称为“订户”。
1.1 事件原理
事件是一种特殊类型的委托,C#采用委托模型来实现事件。发行者首先定义委托,然后使用该委托定义事件,可以简单的认为该事件就是委托,然后,当发行者需要发送(或引发)事件时,就调用事件;而订户则按照事件定义处理程序,然后把处理程序注册到发行者中的事件上,在实例化发行者后,发送(或引用)事件,便会调用相应的事件处理程序。
如下示例(摘取《.Net 之美》)。
// 热水器
public class Heater
{
private int temperature;
public delegate void BoilHandler(int param); //声明委托
public event BoilHandler BoilEvent; //声明事件
// 烧水
public void BoilWater() {
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 95)
{
if (BoilEvent != null)
{ //如果有对象注册
BoilEvent(temperature); //调用所有注册对象的方法
}
}
}
}
}
// 警报器
public class Alarm
{
public void MakeAlert(int param)
{
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", param);
}
}
// 显示器
public class Display
{
public static void ShowMsg(int param)
{ //静态方法
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param);
}
}
class Program
{
static void Main()
{
Heater heater = new Heater();
Alarm alarm = new Alarm();
heater.BoilEvent += alarm.MakeAlert; //注册方法
heater.BoilEvent += (new Alarm()).MakeAlert; //给匿名对象注册方法
heater.BoilEvent += Display.ShowMsg; //注册静态方法
heater.BoilWater(); //烧水,会自动调用注册过对象的方法
}
}
程序的意思是有一个热水器,当水温大于95摄氏度时,发出警报并显示水温。程序定义了热水器类,在其中声明委托和事件,接着定义了报警器类和显示器类,并在主方法中实例它们,进而进行方法注册,在调用热水器加热方法时,便会引发事件,进而执行相应的处理方法。
输出结果如下。
Alarm:嘀嘀嘀,水已经 96 度了:
Alarm:嘀嘀嘀,水已经 96 度了:
Display:水快烧开了,当前温度:96 度。
//省略...
1.2 事件概述
前面简要说明了事件的原理,下面将对事件的几个构成分别叙述。
事件声明:在早期的版本中声明一个事件,必须有一个已经声明好的委托,用以说明事件的类型,在前面热水器程序中就是使用该种方式。在后来的不多发展中,提出了.NET Framework 准则的事件,由于类库中的所有事件均基于 EventHandler 委托,所以只需要声明事件,定义如下:
public delegate void EventHandler(object sender, EventArgs e);
使用后种方式将使得事件的使用较为简单。
事件处理程序:在订户中实现对事件的处理,如前面的报警器类中的方法警报的方法,由于需要向发行者注册,所以必须与委托类型一致(特殊情况是变体,通过协变或逆变,可以实现符合参数的不一致)。
注册事件:将事件处理方法连接到发行者事件上,如前面热水器示所示,将显示水温方法添加到发行者事件上,添加事件时使用“+=”操作符,同样也可使用“-=”撤销事件上的方法。只有当事件引用为空,才会回收其内存变量。
触发事件:调用事件时触发事件。如热水器示所示,调用烧水方法时,进而调用了事件,则触发了事件,进而执行了相应的事件处理方法。
事件存取器:在大多数情况下,是不需要定义事件存取器,因为编译器会自动添加事件访问器。但当每个事件的开销太大时,则需要声明事件存取器,包含 add 和 remove 两种存取器。因为类为每一个事件都声明一个字段,而事件又是引用类型,则需要在堆中开辟空间以存储事件对象,由此便造成资源的浪费。
2 常见事件应用类型
2.1 符合 .NETFramework 准则的事件
2.2 派生类中引发基类事件
2.3 实现接口事件
3 参考
C# 自学手册
C# 编程指南
.Net 之美