Introduction 之前的两篇博客介绍了c#的委托和设计模式中的观察者模式,这一篇谈谈具体的c#事件。
c#的事件监听器们,要求它们必须匹配于事件所要求的返回类型和参数,这个限制是事件定义的一部分,由一个委托指定:
1 2 3 4 5 public delegate void mmmm (object sender, EventArgs e ) ;private void xxxx (object sender, EventArgs e ){ }
sender就是主题类,e就是主题类想要发送给观察者们的数据。
第一个例子(监听事件) 这个例子摘自c#经典入门,此例子将会在每100毫秒调用监听函数 打印一个字符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static int counter = 0 ;static string displayString = "The string will appear one letter at a time." ;static void Main (){ Timer myTimer = new Timer(100 ); myTimer.Elapsed += WriteChar; myTimer.Start(); Console.ReadKey(); } static void WriteChar (object source, ElapsedEventArgs e ){ Console.Write(displayString[counter++ % displayString.Length]); }
注意到c#是使用+=为事件添加处理程序。
其实注册的部分,正确的委托写法应该是: myTimer.Elapsed += new ElapsedEventHandler(WriteChar)
这种语法降低了可读性,编译器会根据事件的上下文来指定它。
第二个例子(自定义事件) 这个例子,我们自定义一个事件,能够让别的类来注册。(我们自定义的事件内部维护着另外一个事件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public delegate void HeartbeatHandler (int count ) ;public class Heart { public event HeartbeatHandler Heartbeat; private Timer pollTimer; private int count = 0 ; public Heart () { pollTimer = new Timer(1880 ); pollTimer.Elapsed += new ElapsedEventHandler(Beat); } public void Start () { pollTimer.Start(); } public void Stop () { pollTimer.Stop(); } private void Beat (object source, ElapsedEventArgs e ) { count++; Console.WriteLine("碰碰.." ); if (Heartbeat!=null ) { Heartbeat(count); } } }; public class Stethoscopes { public void Display (int count ) { Console.WriteLine(string .Format("我听到了第{0}次心跳" ,count)); } } public class TestHeartbeat { static void Main () { var heart = new Heart(); var stethoscopes = new Stethoscopes(); heart.Heartbeat += new HeartbeatHandler(stethoscopes.Display); heart.Start(); Console.ReadKey(); } }
说明
心脏类能够自己每隔1880毫秒跳动一次。如果你注册了Heartbeat事件,就可以获得一个第x跳动的参数进行处理。
注意if (Heartbeat!=null),如果没有事件注册,那么Heartbeat将是null。
两个参数 我们常见的事件函数会有两个参数 object source和Args e。
其中source就是因为引发事件的对象的引用,需要传送这个对象因为我们常常要为由不同对象引发的几个相同事件使用同一个事件处理程序。这时的source就能知道是哪个对象生成了事件。e就类似于刚才的count。
我们的参数可以继承EventArgs:
1 2 3 4 public class HeartbeatEventArgs : EventArgs { .... }
需要注意的是,对于source而言,最好是使用as运算符进行转化,然后在判断是否为空,不用强制类型转化。
其它 其实没有必要自己写委托。.NET提供了两个委托类型EventHandler和EventHander<T>以便于定义事件。它们都是委托,使用标准的事件处理模式。
事件处理函数一般都是void型的,没有返回值。
事件的注册是不分先后的,所以你先注册的事件,不一定先执行。
类似于java中常见的匿名内部类,c#的事件处理,也可以使用匿名方法。
1 2 3 4 header.Heartbeat += delegate (object source, ElapsedEventArgs e) { ... }