如果定义一个事件,就意味着类型要提供以下能力
1.方法可登记它对该事件的关注
2.方法可注销它对该事件的关注
3.该事件发生时,登记了的方法会收到通知
类型之所以能提供事件通知的功能,是因为类型维护了一个已登记方法的列表,事件发生后,类型将通知列表中所有已登记的方法。
CLR的事件是建立在委托的基础上,委托是调用回调方法的一种类型安全的方式,对象通过回调方法来接受它们订阅的通知。
场景:电子邮件应用程序,当电子邮件到达时,用户希望将该邮件转发给传真机或寻呼机
1.1、第一步:定义类型来容纳所有需要发送给事件通知接受者的附加信息
事件引发时,引发事件的对象可能希望向接收事件通知的对象传递一些附加信息。好吧,我们这个类可以这样定义
internal class NewMailEventArgs : EventArgs { private readonly String m_from, m_to, m_subject; public NewMailEventArgs(String from, String to, String subject) { m_from = from; m_to = to; m_subject = subject; } //发件人 public string From { get { return m_from; } } //收件人 public string To { get { return m_to; } } //主题 public string Subject { get { return m_subject; } } }
EventArgs类:
1.2、第二步:定义事件成员
事件成员使用C#关键字event来定义,每个事件成员都要指定以下内容:
1.一个可访问性标识符(几乎肯定是public,因为要暴漏给其他订阅者)
2.一个委托类型,它要指出要调用的方法的原型
internal class MailManager{ //第二步定义事件成员 public event EventHandler<NewMailEventArgs> NewMail; }
.3、第三步:定义负责引发事件的方法来通知事件的登记对象
类应定义一个受保护的虚方法。要引发事件时,当前类及其派生类中的代码会调用该方法。该方法要获取一个参数,也就是一个NewMailEventArgs对象(包括附加信息),该方法默认实现只检查是否有对象登记了对事件的关注,如果有就引发事件,通知登记者
internal class MailManager { public event EventHandler<NewMailEventArgs> NewMail; protected virtual void OnNewMail(NewMailEventArgs e) { //出于线程安全考虑,现将对委托字段引用复制到一个临时字段中 EventHandler<EventArgs> temp = System.Threading.Interlocked.CompareExchange(ref NewMail, null, null); //任何方法登记了对事件的关注,就通知他们 if (temp!=null) { temp(this, e); } } }
为了方便起见,可以定义一个扩展方法来封装这个线程安全逻辑
public static class EventArgExtensions{ public static void Raise<TEventArgs>(this TEventArgs e,Object sender,ref EventHandler<TEventArgs> eventDelegate) where TEventArgs:EventArgs{ //出于线程安全考虑,现将对委托字段引用复制到一个临时字段中 EventHandler<EventArgs> temp = System.Threading.Interlocked.CompareExchange(ref NewMail, null, null); //任何方法登记了对事件的关注,就通知他们 if (temp!=null) { temp(this, e); } } }
现在,可以像下面这样重写OnNewMail方法:
protected virtual void OnNewMail(NewMailEventArgs e){ e.Raise(this,ref m_NewMail); }
1.4、第四步:定义方法将输入转化为期望事件
现在还需要一些方法来获取输入,并把它转化为事件的引发,在MailManager的例子中是调用SimulateNewMail方法来指出一封新的电子邮件已到达MailManager:
internal class MailManager{ //第四步:定义方法将输入转化为期望事件 public void SimulateNewManager(string from ,string to,string subject){ //构造一个对象来容纳想传给通知接受者的信息 NewMailEventArgs e=new NewMailEventArgs(from,to,subject); //调用虚方法通知对象事件已发生 //如果没有类型重写该方法,我们的对象将通知事件的所有登记对象 OnNewMail(e); } }
1.5、第五步:设计订阅者,及回调方法
internal sealed class Fax{ //MailManager传给构造器 public Fax(MailManager mm) { //构造EventHandler<NewMailEventArgs>委托的一个实例 //使他引用我们的FaxMsg回调方法 //向MailManager的NewMail事件登记我们的回调方法 mm.NewMail+= FaxMsg; } //回调方法,新电子邮件到达,MailManager将回调此方法 //'sender'表示MailManager对象,便于将信息回传给它 //'e'表示MailManager对象想传递给我们的附加事件信息 private void FaxMsg(object sender,NewMailEventArgs e) { Console.WriteLine("Faxing mail message:"); Console.WriteLine("From={0}, To={1}, Subject={2}",e.From,e.To,e.Subject); } //Fax对象将向NewMail事件注销自己对它的关注,以后便不再接受通知 public void Unregister(MailManager mm) { //向MailManager的NewMail事件注销自己对这个事件的关注 mm.NewMail-=FaxMsg; } }
1.6、程序入口:
class Program { static void Main(string[] args) { //初始化邮件管理 MailManager mm = new MailManager(); //Fax订阅邮件 Fax f = new Fax(mm); //Pager订阅邮件 Pager p = new Pager(mm); //新邮件到达,通知订阅者,调用它们的回调函数 mm.SimulateNewManager("boss", "all", "meetting"); } }