参考的文章:
http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-in-CSharp.aspx
上面这篇文章讲得很好
委托
在调用一个方法的时候,为了使得方法执行的情况根据我们赋值的不同而不同,简单的办法是在这个方法中使用判断语句(switch— case),这样来根据参数可以得到不同的执行效果,但是一旦所需执行效果的的种类增加了,我们就不得不修改这个方法,这让可扩展性变得很差。
这个时候就用到了委托,委托是一个类型,是把函数当做参数来赋值给另一个方法。这样子,要实现刚才同样的效果,我们只需要在这个函数的外部增加新的我们需要的方法,然后再赋值的时候赋值给这个委托类型的形参就可以得到刚才的效果了。
将方法绑定到委托在文中可以看见这么几种方法:
第一种 像参数一样赋值给方法
GreetPeople(name1, EnglishGreeting);
——————EnglishGreeting是调用的方法
第二种
delegate2 = ChineseGreeting;
GreetPeople("Jimmy Zhang", delegate1);
——————用一个中间的参数,实质上和上面的方法没区别
给委托增加多个方法
delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
——————一定要现有“=”才可以使用“+=”,当调用这个委托的时候,将依次调用其所绑定的方法。
使用下面的方法可以简化这一过程
GreetingDelegate delegate1 = new GreetingDelegate(EnglishGreeting);
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
也可以这么来写:
GreetingDelegate delegate1 = new GreetingDelegate();
delegate1 += EnglishGreeting; // 这次用的是 “+=”,绑定语法。
delegate1 += ChineseGreeting; // 给此委托变量再绑定一个方法
给委托取消绑定的方法
delegate1 -= EnglishGreeting; //取消对EnglishGreeting方法的绑定
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
事件
c#是面向对象的一种语言,面向对象就是要讲究封装,封装的一个功能就是对访问权限的控制,在类里面的字段,为了控制对这些字段的访问权限,我们就建立了对应的属性,那么委托也是一种类型,我们要对这种类型的“字段”进行访问权限的控制,事件就可以理解成一个封装的委托类型
public class GreetingManager{
//这一次我们在这里声明一个事件
public event GreetingDelegate MakeGreet;
public void GreetPeople(string name) {
MakeGreet(name);
}
}
这是这个事件对应的委托,定义在类的外面,而这个事件定义在类的里面,很符合我们再面向对象编程中喜欢把所有东西都定义到类里面的习惯···还有
事件应该由事件发布者触发,而不应该由客户端(客户程序)来触发。事件的发布者是事件定义的类,客户端一般指的是使用这个事件的地方,一般是Main函数里面
//定义委托,它定义了可以代表的方法的类型
public delegatevoid GreetingDelegate(string name);
还是用文中的一个例子来阐述事件
假设我们有个高档的热水器,我们给它通上电,当水温超过95度的时候:1、扬声器会开始发出语音,告诉你水的温度;2、液晶屏也会改变水温的显示,来提示水已经快烧开了。
现在我们需要写个程序来模拟这个烧水的过程,我们将定义一个类来代表热水器,我们管它叫:Heater,它有代表水温的字段,叫做temperature;当然,还有必不可少的给水加热方法BoilWater(),一个发出语音警报的方法MakeAlert(),一个显示水温的方法,ShowMsg()。
但我们需要三个部分分开 就是烧水只管烧水报警只管报警···大概是这个样子
// 热水器
public class Heater {
private int temperature;
// 烧水
private void BoilWater() {
for (int i = 0; i <= 100; i++) {
temperature = i;
}
}
}
// 警报器
public class Alarm{
private void MakeAlert(int param) {
Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:" , param);
}
}
// 显示器
public class Display{
private void ShowMsg(int param) {
Console.WriteLine("Display:水已烧开,当前温度:{0}度。" , param);
}
}
这里就出现了一个问题:如何在水烧开的时候通知报警器和显示器?在继续进行之前,我们先了解一下Observer设计模式,Observer设计模式中主要包括如下两类对象:
- Subject:监视对象,它往往包含着其他对象所感兴趣的内容。在本范例中,热水器就是一个监视对象,它包含的其他对象所感兴趣的内容,就是temprature字段,当这个字段的值快到100时,会不断把数据发给监视它的对象。
- Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。在本范例中,Observer有警报器和显示器,它们采取的行动分别是发出警报和显示水温。
在本例中,事情发生的顺序应该是这样的:
- 警报器和显示器告诉热水器,它对它的温度比较感兴趣(注册)。
- heater.BoilEvent += alarm.MakeAlert; //注册方法 heater.BoilEvent += Display.ShowMsg; //注册静态方法
- 热水器知道后保留对警报器和显示器的引用。
- 热水器进行烧水这一动作,当水温超过95度时,通过对警报器和显示器的引用,自动调用警报器的MakeAlert()方法、显示器的ShowMsg()方法。
Net Framework的编码规范:
- 委托类型的名称都应该以EventHandler结束。
- 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
- 事件的命名为 委托去掉 EventHandler之后剩余的部分。
- 继承自EventArgs的类型应该以EventArgs结尾。
- 这里还有一个约定俗称的规定,就是订阅事件的方法的命名,通常为“On事件名”,比如这里的OnBoiled。
再做一下说明:
- 委托声明原型中的Object类型的参数代表了Subject,也就是监视对象,在本例中是 Heater(热水器)。回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。
- EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature。
上面这些其实不仅仅是为了编码规范而已,这样也使得程序有更大的灵活性。比如说,如果我们不光想获得热水器的温度,还想在Observer端(警报器或者显示器)方法中获得它的生产日期、型号、价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用传给警报器的方法,就可以在方法中直接访问热水器了。
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, BoiledEventArgse);
public event BoiledEventHandlerBoiled; //声明事件
// 定义BoiledEventArgs类,传递给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 = newBoiledEventArgs(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; //给匿名对象注册方法
//下面这种方法可以看书事件,委托,处理方法三者的关系,可以这么来概括:
//事件Boiled一旦引发,那么对应这个事件的委托BoiledEventHandler就会调用相关的方法MakeAlert来处理这个事件
heater.Boiled += newHeater.BoiledEventHandler(alarm.MakeAlert); //也可以这么注册
heater.Boiled += Display.ShowMsg; //注册静态方法
heater.BoilWater(); //烧水,会自动调用注册过对象的方法
}
}
}
*********************************************************************************************************
白话文解释上面的流程(主要针对主函数):----大话烧水委托事件
为了怕烧水烧开没人管,我们需要给这个烧水装置装一个显示和报警部件来监控,以便烧水烧完了就关掉这个烧水的东西
heater.Boiled += alarm.MakeAlert; //注册方法
heater.Boiled += Display.ShowMsg; //注册静态方法
然后我们就开始烧水
public void BoilWater()
{
for (int i = 0; i <= 100; i++)
{
temperature = i;
if (temperature > 95)
{
//建立BoiledEventArgs 对象。
BoiledEventArgs e = new BoiledEventArgs(temperature);
OnBoiled(e); // 调用 OnBolied方法
}
}
}
我们在烧水的过程中一直看着这个水有没有烧开
if (temperature > 95)
在95度以上的时候我们发现烧开了
protected virtual void OnBoiled(BoiledEventArgs e)
{
if (Boiled != null)
{ // 如果有对象注册
Boiled(this, e); // 调用所有注册对象的方法....在这个地方引发了事件
}
}
然后我们就打上触发一个“水已经烧开了”的事件
Boiled(this, e);
this指的是包含这个事件的类
e表示这个事件有关的参数,这里指的是烧水的温度,当然我们把这个温度变量装换成符合委托事件标准的类型了
BoiledEventArgs e = new BoiledEventArgs(temperature);
既然事件爆发了,那么在最开始关联这个事件的两个方法就启用了
// 警报器
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();
}
}
然后我们就看到了程序的执行结果