事件
说到委托不得不说的另一个概念就是事件。
为什么会用到事件
为了能更好的理解为什么要使用事件,下面我们将换一个示例来说明(这个示例来源于网络),事件与委托的关系,以及为何要使用事件。
示例:现在,我们有一个热水器。这个热水器有热水的功能,同时,当温度高于96度后会发出警报,并显示当前水温。由OO的思想我们可以拆分为三个对象。
1、 热水器:具有烧水的功能,和水温属性
2、 报警器:是热水器的组件之一,具有发出警报的功能
3、 显示器:是热水器的组件之一,具有显示当前温度功能
下面我们用代码来实现一下三个类的基本功能
using System;
usingSystem.Collections.Generic;
using System.Text;
namespaceDelegate
{
// 热水器
public class Heater
{
privateint temperature;
// 烧水
public void BoilWater()
{
}
}
// 警报器
public class Alarm
{
public void MakeAlert(intparam)
{
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", param);
}
}
// 显示器
public class Display
{
public static void ShowMsg(int param)
{ //静态方法
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param);
}
}
这个时候我们可以看到,显示器和警报器都提供了一个无返回值,参数个数为1且类型为int的变量。
按要求,这时我们可能会有这种实现,即在Heater的BoilWater方法中,当温度达到一定条件时,就调用其它两个类的方法,代码如下。
public void BoilWater()
{
for(int i = 0; i <= 100; i++)
{
temperature = i;
if(temperature > 95)
{
Alarmalm = new Alarm();
alm.MakeAlert(temperature);
Display.ShowMsg(temperature);
}
}
}
但是前面我们才学过委托,也知道如果这样做不利于扩展对吧。所以我们可以像如下这样做修改。
// 热水器
public class Heater
{
privateint temperature;
public delegate void BoilHandler(intparam); //声明委托
// 烧水
public void BoilWater()
{
for(int i = 0; i <= 100; i++)
{
temperature = i;
if(temperature > 95)
{
if(BoilHandler!= null)
{ //如果有对象注册
BoilHandler (temperature); //调用所有注册对象的方法
}
}
}
}
}
这时,我们会发现BoilHandler会报错。本来我们是希望在Heater被实例化的时候再决定有那些事件需要在温度高于95度的时候触发的。但委托确需要我们在使用的时候就要实例化,而不能等到调用的时候才做这样的事情。所以我们就必须用到事件这一概念了,下面先看下事件如何申明
public event BoilHandlerBoilEvent; //声明事件
可以看到事件申明只不过是在委托前加了一个event关键字并且取了一个别名而已。不过正是因为这个别名,才使得可以从类的外部为委托BoilHandler增加方法绑定,具体代码如下
public class Heater
{
privateint temperature;
public delegate void BoilHandler(intparam); //声明委托
public event BoilHandlerBoilEvent; //声明事件
// 烧水
public void BoilWater()
{
for(int i = 0; i <= 100; i++)
{
temperature = i;
if(temperature > 95)
{
if(BoilEvent != null)
{ //如果有对象注册
BoilEvent(temperature);//调用所有注册对象的方法
}
}
}
}
}
所以,可以理解事件为委托的别名,然后公开到类的外部供其调用。有人会说,那委托BoilHandler是public的,也可以调用呀。其实不是的,如果你在外部实例后你会发现,调用不了这个委托,因为委托不管你是public还是什么,在编译的时候都会变成privite这是出于安全考虑,所以只有借助事件才能公开出来。下面我们看看在客户端如何使用。
class Program
{
static void Main(string[]args)
{
Heaterheater = new Heater();
Alarmalarm = new Alarm();
heater.BoilEvent +=alarm.MakeAlert; //注册方法
heater.BoilEvent += (new Alarm()).MakeAlert;//给匿名对象注册方法
heater.BoilEvent += Display.ShowMsg; //注册静态方法
heater.BoilWater(); //烧水,会自动调用注册过对象的方法
Console.ReadKey();
}
}
输出为:
Alarm:嘀嘀嘀,水已经 96 度了:
Alarm:嘀嘀嘀,水已经 96 度了:
Display:水快烧开了,当前温度:96度。
这样即使以后有其它组件加入进来,也只需要在main方法中使用+=的方法把需要执行的新方法注册一下就可以了。
看到这里,大家可能会想,这与我们在.NetFramework中看到的事件不一样呢,那里面的事件一般都有两个参数一个是object类型,另一个是EventArgs或其子类型。那我们下面就来讲讲.NetFramework中的事件模型是怎么样的。
.Net Framework中的事件模型
在.NetFramework中有以下特点需要我们注意
1、委托类型的名称都应该以 EventHandler结束
2、委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object类型,一个EventArgs类型或其子类。
3、事件的命名为 委托名去掉EventHandler之后剩余的部分
4、继承于EventArgs的类型应该以EventArgs结尾
特别说明:Object一般是指触发事件的对象,而EventArgs则是触发事件后需要传递的参数。
为了让我们的代码和.NetFramework的事件模型一致,我们可以做如下修改。
1、 自定义参数类型
2、 按.NetFramework中的事件模型实现事件触发
using System;
usingSystem.Collections.Generic;
using System.Text;
namespaceDelegate
{
// 热水器
public class Heater
{
privateint temperature;
public string type = "RealFire001"; // 添加型号作为演示
public string area = "ChinaXian"; // 添加产地作为演示
//声明委托
public delegate void BoiledEventHandler(Objectsender, BoliedEventArgs e);
public event BoiledEventHandlerBoiled; //声明事件
// 定义BoliedEventArgs类,传递给Observer所感兴趣的信息
public class BoliedEventArgs: EventArgs
{
publicreadonly inttemperature;
publicBoliedEventArgs(int temperature)
{
this.temperature= temperature;
}
}
// 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视
protectedvirtual voidOnBolied(BoliedEventArgs e)
{
if(Boiled != null)
{ // 如果有对象注册
Boiled(this,e); // 调用所有注册对象的方法
}
}
// 烧水。
public void BoilWater()
{
for(int i = 0; i <= 100; i++)
{
temperature = i;
if(temperature > 95)
{
//建立BoliedEventArgs 对象。
BoliedEventArgse = new BoliedEventArgs(temperature);
OnBolied(e); // 调用 OnBolied方法
}
}
}
}
// 警报器
public class Alarm
{
public void MakeAlert(Objectsender, Heater.BoliedEventArgse)
{
Heaterheater = (Heater)sender; //这里是不是很熟悉呢?
//访问 sender 中的公共字段
Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type);
Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", e.temperature);
Console.WriteLine();
}
}
// 显示器
public class Display
{
public static void ShowMsg(Object sender, Heater.BoliedEventArgs e)
{ //静态方法
Heaterheater = (Heater)sender;
Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
Console.WriteLine();
}
}
class Program
{
static void Main()
{
Heaterheater = new Heater();
Alarmalarm = new Alarm();
heater.Boiled += alarm.MakeAlert; //注册方法
heater.Boiled += (new Alarm()).MakeAlert;//给匿名对象注册方法
heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
heater.Boiled += Display.ShowMsg; //注册静态方法
heater.BoilWater(); //烧水,会自动调用注册过对象的方法
}
}
}
输出为:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:96度。
// 省略 ...
在上面的例子中,大家会发现,除了可以使用参数 e来获取所需要参数外,还可以能过 sender来获取触发事件对象本身的一些信息。是不是很爽呀。