C# 委托和事件 笔记
参考:C#事件解析 徐洪军
上面的几位前辈写的文章都很好,这里只是总结一下。
一、什么是委托和事件
委托仅仅是函数指针,它能够引用函数,通过传递地址的机制完成,委托是一个类,对委托实例化时,要提供一个引用函
数,将它作为构造函数的参数。
每一个委托都有一个签名,被引用的函数必须和委托有相同的签名。
Delegate int SomeDelegate(string s,bool b);
private int SomeFunction(string str,bool bln){..}
SomeDelegate sd=new SomeDelegate(SomeFunction);
SomeFunction已经被SomeDelegate注册,如果调用sd,那么SomeFunction也会被调用。
总之,委托可以理解为函数指针,和函数指针不同的是委托是面向对象的。
事件简单分为两个部分:事件发生的类和事件接收的类。发生的类就是在这个类里面触发事件,但是它不知道究竟是哪个
对象或方法会处理这个事件。接收的类有一个处理事件的方法。
二、使用事件的步骤
- 创建一个委托
- 将委托与特定的事件关联
- 编写事件处理程序
- 利用事件处理程序生成一个委托实例
- 把委托实例添加到产生事件对象的事件列表中
A产生事件,事件为a,B接受事件,b处理;当a事件产生后,A通过事件列表中的委托对象将事件通知给B,B接收到一个事
件通知并调用b来处理。
public class A { public delegate void EventHandler(object sender); public event EventHandler a; public void run() { a(this); } } class B { private void b(object sender) { ... } public B(A a) { a.a+=new A.EventHandler(this.b); } }
三、事件和委托的关系
事件是对委托的封装,事件底层靠委托实现。
事件是委托类型的一个变量,所以必须声明在类的内部,因为事件本来就是一个委托,所以可以将赋给委托的方法赋给事件。
不管将委托声明为public还是protected,它总是在编译的时候被声明为private。所以,只能在声明事件的类的内部对事件进行"="赋值操作。
委托封装了两个操作"+="和"-=",这两个操作专门用来在类的客户端注册方法。
委托在编译的时候会编译成类。
有了委托为什么还要有事件?
事件是一种委托链,只能够用+=或者-=,防止之前添加的委托被覆盖。
如果不想让事件被人在类外面随便操作,必须使用事件来定义。
四、使用事件和委托的典型例子
例子一:按键事件
首先,有几点要说明一下,1.EventArgs是包含事件数据的类的基类,如果事件处理程序需要状态信息,则应用程序必须从此类派生一个类来保存数据。2.object sender的sender 是事件源,表示触发此事件的对象;EventArgs e的e是事件参数,包含该事件相关的信息,比如参数,它用来辅助你处理事件,还可以传递引用,在方法中直接访问类的成员等。
步骤就是:创建一个事件发生的类,定义委托和事件,里面的某一个动作会触发事件;创建一个接收事件的类,生成一个委托实例,并添加到事件列表中。
internal class KeyEventArgs:EventArgs { private char keychar; public KeyEventArgs(char keyChar):base() { this.keyChar=keyChar; } public char KeyChar { get { return keyChar; } } } internal class KeyInputMointor { public delegate void KeyDownEventHandler(object sender,KeyEventArgs e); public event KeyDownHandler KeyDown; public void run() { bool finished=false; while(!finished) { Console.WriteLine("Input a char..."); string response=Console.ReadLine(); char responseChar=(response=="")?'':char.ToUpper(response[0]); if(responseChar=='X') finished=true; else{ //包含事件的数据 KeyEventArgs keyEventArgs=new KeyEventArgs(responseChar); //触发事件 KeyDown(this,keyEventArgs); } } } internal class EventReceiver { public EventReceiver(KeyInputMonitor monitor) { //产生一个委托实例,并添加到事件列表中 monitor.KeyDown+=new KeyInputMonitor.KeyDownHandler(this.onKeyDown); private void OnKeyDown(object sender,KeyEventArgs e) { Console.WriteLine("Capture Key:{0}",e.KeyChar); } } public class MainEntryPoint { public static void Start() { //事件发送器 KeyInputMointor monitor=new KeyInputMonitor(); //事件接收器 EventReceiver eventReceiver=new EventReceiver(monitor); //运行 monitor.run(); } }
例子二、热水器烧水事件
public class Heater { private int temperature; //声明事件和委托 public delegate void BoiledEventHandler(Object sender,BoiledEventArgs e); public event BoiledEventHandler Boiled; //传递温度数据 public class BoiledEventArgs:EventArgs { public readonly int temperture; public BoidEventArgs(int temperature) { this.temperature=temperature; } } //可供子类重写 protected virtual void OnBoiled(BoiledEventArgs e) { //如果有对象注册,就触发Boiled事件,即调用所有注册对象的方法 if(Boiled!=null) { Boiled(this,e); } } public void BoilWater() { for(int i=0;i<100;i++) { temperature=i; if(temperature>95) { //new一个EventArgs对象并传递给OnBoiled函数 BoiledEventArgs e=new BoiledEventArgs(temperature); OnBoiled(e); } } } } //警报器 public class Alarm { public void MakeAlert(Object sender,Heater.BoiledEventArgs e) { Console.WriteLine("Alarm: the temperature now is {0} ",e.temperature); } } //显示器 public class Display { public static void ShowMsg(Object sender,Heater.BoiledEventArgs e) { Console.WriteLine("Display:the current is {0} temperature",e.temperature); } } 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.BoiledWater(); Console.ReadKey(); } }
五、观察者模式
观察者模式是为了定义对象间的一种一对多的依赖关系。它是一种松耦合的设计模式。
observer在subject里面注册,subject某事件发生时通知observer,observer采取相应的行动。
上一节里面热水器烧水的例子中,Heater就是subject,Alarm和Display就是observer。首先Alarm和Display给Heater进行注册,然后水温高于95度时会通知Alarm和Display,它们分别调用MakeAlarm和ShowMsg方法。