春节过去了,继续去年的学习。。。。
轮到事件了,这个东西从我一开始接触.net时就常常听到别人提起,以前不知道怎么回事,现在感觉C#事件是.net中非常重要的一块,像asp.net ,winform等我们常拖的控件,都是基于事件驱动处理的封装好的框架。
什么是事件
宏观的理解就是,如果类型定义了事件成员,那么类型或类型的实例就可以通知其他对象发生了特定的事情。
记得上学时老师教过一种设计模式——观察者模式,跟事件很像,一个观察者发现老板来了,然后通知所有跟他打过招呼的员工老板来了,要干活啦。
在CLR中事件模型是建立在委托的基础上的。委托这个东西很牛逼,在下一节接着讲。。。
说起来似乎都可以听得懂,还是举一个常用的例子来实际操作一下比较好,场景:一封新邮件到达,希望也同时转发给传真机或寻呼机。
这样的话需要有个邮件转发中心来转发新邮件,命名为MailManager。
还的有2个对象传真机and寻呼机。命名为Fax和Pager
既然是邮件那肯定得有发件人,收件人,邮件标题等等。咱们玩的是事件,C#中有个EventArgs类来专门容纳所有需要发送事件通知者的附加信息。继承它重新派生一个新的类命名时以EventArgs结束。。
下面是整个场景的构建思路,如图:
这样的话思路就比较清晰了,接下来就是如何玩事件并学好掌握它。
1.第一步先来把这个邮件信息(即容纳所有需要发送给事件通知者的附加信息)对象给完成。注意派生自EventArgs命名时必须以EventArgs结尾,书上这么说的我也不知道为什么,西西。
internal class NewMailEventArgs:EventArgs { private readonly String m_from, m_to, m_subject; public NewMailEventArgs(String m_from,String m_to,String m_subject) { this.m_from = m_from; this.m_to = m_to; this.m_subject = m_subject; } public String m_From { get { return m_from; } } public String m_To { get { return m_to; } } public String m_Subject { get { return m_subject; } } }
2.好接下来第二步就是邮件中转,在这里定义了事件以及事件的触发。
以下简单的几行代码需要注意的地方:
- 很显然,这个事件在大多数情况下是公开的,这样其他的代码才能访问该事件成员
- 还有一个很重要的类型EventHandler<NewMailEventArgs> ,首先他是一个委托类型,这就意味这所有事件通知的接受者都必须提供一个与EventHandler<NewMailEventArgs>委托类型匹配的回调方法,这里EventHandler是泛型委托类型其定义如下
public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e) where TEventArgs:EventArgs
所以方法原型必须是viod Method(object sender,NewMailEventArgs e);
- 在定义引发事件的方法时出于线程安全的考虑将委托字段NewMail的引用复制到临时字段Temp中,temp(this,e)如果谁订阅了事件,则就会执行谁的回调方法。
- OnNewMail方法有一关键字virtual目的是为了使派生类可以重写该方法,这个能力使派生类能控制事件的引发,从而以自己的方式处理电子邮件。
internal class MailManager { //事件的定义 public event EventHandler<NewMailEventArgs> NewMail; //引发事件的方法 protected virtual void OnNewMail(NewMailEventArgs e) { EventHandler<NewMailEventArgs> temp = Interlocked.CompareExchange(ref NewMail, null, null); if (temp!=null) { temp(this, e); } } public void SimulateNewMail(String from, String to, String subject) { NewMailEventArgs e = new NewMailEventArgs(from, to, subject); OnNewMail(e); } }
接下来定义一个方法来接受邮件信息的来转化为事件
internal class MailManager { //其他成员... public void SimulateNewMail(String from, String to, String subject) { //构造一个对象来容纳想传给通知接受者的信息 NewMailEventArgs e = new NewMailEventArgs(from, to, subject); //调用虚方法通知对象事件已发生 //如果没有类型重写该方法,我们的对象将通知事件的所有登记对象 OnNewMail(e); } }
在设计侦听事件的类型,Fax和Pager对象之前还是有必要了解下编译器是如何实现事件的,就是说你定义一个事件很简单,但是它是如何工作的???
定义一个事件只需一行代码,C#编译器会把它转换为3个构造
- 初始化事件为null,即
private event EventHandler<NewMailEventArgs> NewMail=null; 这里始终是private
- 一个公共add_Xxx方法(其中Xxx是事件名),它允许其他对象登记对事件的关注,它的可访问性与定义事件时的访问性一致,如果是pulbic 则public void add_Xxx(EventHandler<TEventArgs> value)
- 一个公共remove_Xxx方法(Xxx是事件名),它允许一个对象注销对事件的关注,同上。。。。
3.设计侦听事件的类型,Fax和Pager对象
mm.NewMail+=FaxMsg;
等价于
mm.add_NewMail(new EventHandler<NewMailEventArgs>(this.FaxMsg));
internal class Fax { public Fax(MailManager mm) { mm.NewMail += this.FaxMsg; } private void FaxMsg(Object sender, NewMailEventArgs e) { Console.WriteLine("Fax mail message:"); Console.WriteLine("From={0},To={1},Subject={2}",e.m_From,e.m_To,e.m_Subject); } }
4.Main函数入口
class Program { static void Main(string[] args) { MailManager mm = new MailManager(); Fax f = new Fax(mm); Pager p = new Pager(mm); mm.SimulateNewMail("me", "all", "hello world"); } }
接下来还有显示实现事件
就是说开发人员可以控制add和remove方法操纵回调委托的方式,可以提高执行效率。
例如某个类型含有大量的事件,为了高效率存储事件委托,公开了事件的每个对象都要维护一个集合(通常是一个字典),这个集合将某种形式的事件标识符作为Key,将一个委托列表作为Value。。。