C#事件的发送方和接收方(订阅方)【转】
基于Windows的应用程序也是基于消息的,Windows使用预定义消息与应用程序通讯。.NET Framework将Windows消息封装在事件中,可以把事件作为对象之间的通讯介质。
事件发送方:发送事件的对象;
事件接收方:捕获事件并对其作出响应的对象(处理事件);
在事件通讯机制中,事件发送方不知道哪个对象将接收到它引发的事件以及进行什么样的处理,事件发送方不知道谁将是事件接收方,它只是将"事件发生了"这个消息广播出去。
在C#中,事件机制是借助委托来实现的。一个事件就相当于一个委托实例。
----------------------------------------------------------------------
使用事件:使用事件分为4步,与使用委托有一点小区别。
1.定义一个委托类型。
这一步与使用委托没有什么不同,一般都使用 .Net预定义的委托。
2.定义一个事件名(在事件发送方中)。
访问控制符 event 委托类型名 事件名;
3.封装事件,即将事件处理方法注册到事件中(事件处理方法定义在事件接收方中)。
事件发送方.事件名 += new 委托类型名(事件处理方法);
事件名即相当于委托实例名,事件处理方法即相当于委托实例的关联方法。只不过,在事件机制中,将定义委托实例(使用委托的第二步)分为两个步骤,分别由事件发送方和接收方来进行: 定义一个事件名 - 得到一个委托实例名 (事件发送方) ;封装该事件 - 得到一个关联方法(将一个事件处理方法与一个事件关联) (事件接收方)。
注:在事件发送方定义事件名时,可以将事件名声明为static的,以便事件接收方封装事件
4.事件发送方引发事件,事件接收方的事件处理方法捕获并处理事件。
引发事件(相当于调用委托实例)即会导致事件处理方法的执行(相当于委托实例的关联方法的执行)
事件可能由用户的操作(比如单击鼠标)引发,也可能由某些其他的程序逻辑触发;
--------------------------------------------------------------------
using System; namespace EventExample { class ClassReceive //事件接收方 { [STAThread] static void Main(string[] args) { ClassSend Send = new ClassSend(); //3.封装事件 Send.CalculateFinished += new MyDelegate(Send_CalculateFinished); //执行下面三行,将导致三次引发ClassSend的CalaculatedFinished事件 Send.Square(2); //调用事件发送方的代码,导致事件被引发 Send.Cube(2); //调用事件发送方的代码,导致事件被引发 Send.Double(2); //调用事件发送方的代码,导致事件被引发 } //定义事件处理方法,在事件被引发后执行 private static void Send_CalculateFinished(string msg) { Console.WriteLine(msg + "计算完成"); } } //1.定义委托,指定返回类型和形参列表。与类定义一样,同在命名空间下 delegate void MyDelegate(string msg); class ClassSend //事件发送方 { //2.定义事件CalculateFinished,该事件属于ClassSend类 public event MyDelegate CalculateFinished; //4.执行该方法事件将被引发,一般由事件接受方调用该方法来触发事件 public void OnCalculateFinished(string msg) { //判断事件是否为空,事件接受方如果定义了事件的处理方法,则不为null,//如果事件为空就触发事件,将导致一个空引用异常 if (CalculateFinished != null) { CalculateFinished(msg); //就是这一句引发事件,即: 事件名(形参列表); 此方法的形参列表已经由与事件相关的委托指定了 } } public void Square(float x) { float result = x * x; Console.WriteLine("{0}的平方等于:{1}", x, result); //执行事件引发方法,将导致事件被引发 OnCalculateFinished("平方"); } public void Cube(float x) { float result = x * x * x; Console.WriteLine("{0}的立方等于:{1}", x, result); //执行事件引发方法,将导致事件被引发 OnCalculateFinished("立方"); } public void Double(float x) { float result = 2 * x; Console.WriteLine("{0}的倍数等于:{1}", x, result); //执行事件引发方法,将导致事件被引发 OnCalculateFinished("倍数"); } } }
--------------------------------------------------------------------
事件是多点委托,对事件只能使用 += 和 -= 运算,= 运算对于事件是无效的。
.NET 提供了一个预定义的用于事件的委托类型 - EventHandler:public delegate void EventHandler(Object sender,EventArgs e);
参数sender是引发事件的事件发送方对象,e是事件的有关数据;在定义事件时,可以不必自定义委托类型,直接使用这个预定义的事件委托类型就可以了。
自定义事件时,应该遵守一些命名规范:
使用动词命名事件,最好带上现在、进行或完成时态来描述事件的触发时效;
事件的委托类型的命名一般以"EventHandler"为后缀,事件参数类名称以"EventArgs"为后缀;
总是使用 sender 和 e 来命名事件中的两个参数;
事件发送方中,引发事件的方法的命名一般用 On + 事件名 eg:OnCalaculateFinished(string msg);
在事件发送方类中,定义引发事件的方法(其中包含调用事件的语句)时,必须先判断事件是否为空,因为事件是在接收方中封装的(定义事件处理方法),事件发送方无法知晓是否存在有事件处理方法(发送方不知道接收方的存在),所以在事件发送方中引发事件的代码处,必须先判断事件是否为空。另外,往往是在事件接收方中,调用了事件发送方的代码,然后导致事件被引发,因而,在事件接收方中,封装事件的代码应该先于调用事件发送方的代码执行。
自己画了一张图,看图能让结构更清晰一些: