委托与事件代码详解
using System;
using System.Collections.Generic;
using System.Text;
namespace @Delegate //自定义命名空间,新建控制台程序,命名后自动添加
{
// 热水器
public class Heater
{
private int temperature;
public string type = "RealFire 001"; // 添加型号作为演示
public string area = "China Xian"; // 添加产地作为演示
//声明委托
public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e);/*BoiledEventHandler相当于一个类型(属于委托),与String地位等同,它所声明的参数形式与后来它要包含的方法的参数形式必须是一致的,例如黄色加亮部分的方法*/
//声明事件
public event BoiledEventHandler Boiled; /* Boiled相当于封装BoiledEventHandler类型的对象(变量) ,使之在类的内部总是pravite的,而使+=和-=的访问权限为声明时的修饰符权限*/
// 定义BoiledEventArgs类,传递给Observer所感兴趣的信息。//Observer设计模式
public class BoiledEventArgs : EventArgs
{
public readonly int temperature;
public BoiledEventArgs(int temperature)
{
this.temperature = temperature;
}
}
// 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视//虚方法可以在类中覆盖重写
protected virtual void OnBoiled(BoiledEventArgs e)
{
if (Boiled != null)
{ // 如果有对象注册
Boiled(this, e); // 调用所有注册对象的方法
}
}
// 烧水。
public void BoilWater()
{
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 95)
{
//建立BoiledEventArgs 对象。
BoiledEventArgs e = new BoiledEventArgs(temperature);
OnBoiled(e); // 调用 OnBolied方法
}
}
}
}
// 警报器
public class Alarm
{
public void MakeAlert(Object sender, Heater.BoiledEventArgs e)
{
Heater heater = (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.BoiledEventArgs e)
{ //静态方法
Heater heater = (Heater)sender;
Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type);
Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
Console.WriteLine();
}
}
class Program
{
static void Main()
{
Heater heater = new Heater();
Alarm alarm = new Alarm();
heater.Boiled += alarm.MakeAlert; //注册方法
heater.Boiled += (new Alarm()).MakeAlert; //给匿名对象注册方法
heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
heater.Boiled += Display.ShowMsg; //注册静态方法
heater.BoilWater(); //烧水,会自动调用注册过对象的方法
Console.ReadKey();
}
}
}
事件就类似于对委托变量的一个封装:
首先,事件是一个委托类型的变量,所以必须声明在类的内部,因为事件本身就是一个委托,那么自然可以将赋给委托的方法赋给事件。
而这个(封装了的委托)变量与普通的委托类型变量又有所不同:
1、不管你将它声明为public还是protected,它总是会在编译的时候被声明为private。所以,如果直接对事件进行“=”赋值语法,只能在声明事件的类内部进行。
2、它封装了两个操作“+=”和“-=”,public和protected的访问声明仅仅是针对于对它的“+=”和“-=”操作。 这两个操作专用于在类的客户端注册方法。
如果你不使用事件,使用一个public的委托,也可以实现上面的操作,但是类的封装性不好,在类的外部可以直接给委托变量赋值。其次就是语法会很奇怪,因为给第一个方法注册用的是“=”赋值语法(因为要进行实例化),而第二次注册方法用的是“+=”,注册语法。
使用事件的时候就解决了上面两个问题,只需要记得使用“+=”来注册方法就可以了。
实际上,委托在编译的时候确实会编译成类。因为Delegate是一个类,所以在任何可以声明类的地方都可以声明委托。
涉及到的知识点:
Observer设计模式简介
Observer设计模式,Observer设计模式中主要包括如下两类对象:
Subject:监视对象,它往往包含着其他对象所感兴趣的内容。在本范例中,热水器就是一个监视对象,它包含的其他对象所感兴趣的内容,就是temprature字段,当这个字段的值快到100时,会不断把数据发给监视它的对象。
Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。在本范例中,Observer有警报器和显示器,它们采取的行动分别是发出警报和显示水温。
在本例中,事情发生的顺序应该是这样的:
警报器和显示器告诉热水器,它对它的温度比较感兴趣(注册)。
热水器知道后保留对警报器和显示器的引用。
热水器进行烧水这一动作,当水温超过95度时,通过对警报器和显示器的引用,自动调用警报器的MakeAlert()方法、显示器的ShowMsg()方法。
类似这样的例子是很多的,GOF对它进行了抽象,称为Observer设计模式:Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。Observer模式是一种松耦合的设计模式。
.Net Framework中的委托与事件
.Net Framework的编码规范:
委托类型的名称都应该以EventHandler结束。
委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。 //许多事件处理函数都是这样的
事件的命名为 委托去掉 EventHandler之后剩余的部分。
继承自EventArgs的类型应该以EventArgs结尾。
再做一下说明:
委托声明原型中的Object类型的参数代表了Subject,也就是监视对象,在本例中是 Heater(热水器)。回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。
EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature。
上面这些其实不仅仅是为了编码规范而已,这样也使得程序有更大的灵活性。比如说,如果我们不光想获得热水器的温度,还想在Observer端(警报器或者显示器)方法中获得它的生产日期、型号、价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用传给警报器的方法,就可以在方法中直接访问热水器了。
总结:
现在来看看许多函数头中的参数(Object sender,EventArgs e)
Object sender
sender是事件源(被监视的对象,也叫监视对象,事件触发者,本例为热水器,水温达到95℃以上触发事件),表示触发此事件的对象
//比如说你按下按钮,那么sender就是按钮,触发已经预定好的事件处理代码,比如Onclik
EventArgs e
e是事件参数(***EventArgs类对象,根据事件的不同事件参数类型可能不同,但必须继承EventArgs类,比如本例中的 public class BoiledEventArgs : EventArgs ,又根据net的编码规范"继承自EventArgs的类型应该以EventArgs结尾。",所以名称为***EventArgs),包含跟该事件相关的信息,比如参数。这要你自己手动去写代码(已经写好封装了的可以直接拿来用,但如果这样,那么它的名字不再是EventArgs了,而是以EventArgs结尾的一个名称。根据.net编码规范,如果一个函数头中直接使用的是EventArgs,那么代表它不需要使用e来传递特殊参数,你可以在VS中看看EventArgs的定义,看看它都包括了什么内容),它用来辅助你处理事件。还可以传递引用,在方法中直接访问类的成员等。
本例中包含参数temperature。
public class BoiledEventArgs : EventArgs
{
public readonly int temperature;
public BoiledEventArgs(int temperature)
{
this.temperature = temperature;
}
}
//假如"用鼠标点击窗体"这个事件发生,那么e会包含点击的位置等等
归根究底,这个sender和e及其一整套的处理方式,只不过是windows消息机制的另外一种表现罢了!