文件涉及的内容:
设计公开事件类型
编译器如何实现事件
设计侦听事件的类型
显式实现事件
事件:定义了事件成员的类型允许类型通知其他对象发生特定的事情。
CLR事件模型以委托为基础,委托是调用回调方法的一种类型安全的方式,对象凭借调用方法接收他们订阅的通知。
定义了事件成员的类型要求能够提供以下功能:
方法能登记它对事件的关注
方法能注销它对事件的关注
事件发生时,登记的方法将收到通知
本文章以一个电子邮件应用程序为例。当电子邮件到达时,用户希望将邮件转发给传真机或寻呼机进行处理。先设计MainlManager类型来接收传入的电子邮件,它公开NewMain事件。其他类型(Fax或Pager)对象登记对于该事件的关注。MailManager收到新电子邮件会引发该事件,造成邮件分发给每个已登记的对象,它们都有自己的方式处理邮件。
1.1设计要公开事件的类型
第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息
该类型通常包含一组私有字段以及一些用于公开这些字段的只读公共属性。
1 class NewMailEventArgs:EventArgs 2 { 3 private readonly string m_from, m_to, m_subject; 4 public NewMailEventArgs(string from,string to,string subject) 5 { 6 m_from = from; 7 m_to = to; 8 m_subject = subject; 9 } 10 public string From { get { return m_from; } } 11 public string To { get{ return m_to; } } 12 public string Subject { get { return m_subject; } } 13 }
第二步:定义事件成员
class MailManager { public event EventHandler<NewMailEventArgs> NewMail; }
其中NewMail是事件名称。事件成员类型是EventHandler<NewMailEventArgs>说明事件通知的所有接收者都必须提供一个原型和其委托类型匹配的回调方法。由于泛型System.EventHandler委托类型的定义如下:
public delegate void EventHandler<TEventArgs>(Object sender,TEventArgs e);
所以方法原型必须具有以下形式:void MethodName(Object sender,NewMailEventArgs e);之所以事件模式要求所有事件处理程序的返回类型都是void,是因为引发事件后可能要调用好几个回调方法,但没办法获得所有方法的返回值,返回void就不允许回调方法有返回值。
第三步:定义负责引发事件的方法来通知事件的登记对象
1 /// <summary> 2 /// 定义负责引发事件的方法来通知事件的登记对象,该方法定义在MailManager中 3 /// 如果类是密封的,该方法要声明为私有和非虚 4 /// </summary> 5 /// <param></param> 6 protected virtual void OnNewMail(NewMailEventArgs e) 7 { 8 //出于线程安全考虑,现在将委托字段的引用复制到一个临时变量中 9 EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail); 10 if(temp!=null) 11 { 12 temp(this, e); 13 } 14 }
上面方法使用了Volatile.Read()方法确保线程安全,主要考虑下面两种情况:
1.直接判断NewMail!=null,但在调用NewMail之前,另一个线程可能从委托链中移除了一个委托,使其为空,从而发生(NullReferenceException)异常。
2.有些人可能也会将其保存在一个临时变量中,但未使用Volatile,理论上可以但是如果编译器发生优化代码移除该临时变量,那就和第一种情况一样。
使用Volatile.Read会强迫NewMail在这个调用发生时读取,引用必须复制到temp变量中,比较完美的解决方式。但是在单线程的中不会出现这种情况
第四步 定义方法将输入转化为期望事件
1 public void SimulateNewMail(string from,string to,string subject) 2 { 3 //构造一个对象来容纳想传给通知接收者的信息 4 NewMailEventArgs e = new NewMailEventArgs(from, to, subject); 5 //调用虚方法通知对象事件已反生 6 //如果没有类型重写该方法 7 //我们的对象将通知事件的所有登记对象 8 OnNewMail(e); 9 }
该方法指出一封新的邮件已到达MailManager。
1.2 编译器如何实现事件
在MailManager类中我们用一句话定义事件成员本身:public event EventHandler<NewMailEventArgs> NewMail;
C#编译器会转换为以下代码: