第二部分 设计类型:第11章 事件

事件是实现交互的类型成员。


定义事件需要提供以下能力:
1.方法可登记/注销它对该事件的关注。
2.改事件发生时,登记了的方法会收到通知。

类型之所以能提供事件通知功能,是因为类型维护了一个已登记方法的列表。事件发生后,类型将通知列表中所有已登记的方法。

为了理解事件在CLR中的工作机制,举个实用的场景:
假定要设计一个电子邮件程序。电子邮件到达时,用户希望将该邮件转发给传真机或寻呼机。
构建这个应用程序时,假定先设计一个名为MailManager的类型,负责接收传入的电子邮件。MailManager类型公开了一个名为NewMail的事件。其他类型(如Fax和Pager)的对象可登记它们对这个事件的关注。MailManager收到信邮件时,会引发该事件,将邮件分发给每一个登记的对象。

应用程序初始化时,只实例化一个MailManager实例。然后,应用程序可实例化任意数量的Fax和Pager对象。

 

11.1 设计要公开事件的类型

11.1.1  第1步:定义类型来容纳所有需要发送给事件通知接收者的附加信息
事件对象想向接收通知的对象传递一些附加信息,那么需要封装到自己的类中,包含私有字段和只读公共属性。根据约定,这种类型应该从System.EventArgs类派生,并且类名以EventArgs结束。

//首先 定义类型来容纳发送给接受者的附加信息
internal class NewMailEventArgs:EventArgs{
  //附加信息的私有字段
  private readnoly string m_from,m_to,m_subject;
 
  //附加信息的只读属性
  public string From{ get{return m_from;}}
  public string To{ get{return m_to;}}
  public string subject { get{return m_subject;}}  
 
  public NewMailEventArgs(string from,string to,string subject){
     m_from = from; m_to = to; m_subject = subject;
  }
}

//后续的步骤在MailManager类中进行
internal class MailManager{
}

 


11.1.2  第2步:定义事件成员
事件成员使用event关键字来定义。


每个事件成员要指定一下内容:
1.一个可访问性标示符(public)。
2.一个委托类型,它指出要调用的方法的原型。
3.一个名称(可以是任意有效的标示符)。

MailManager类的事件成员:

internal class MailManager{
  //第2步:定义事件成员
  public event EventHandler<NewMailEventArgs> NewMail;
  ...
}


NewMail是这个事件的名称。
事件成员的类型是EventHandler<NewMailEventAgrs>,意味着“事件通知”的所有接受者都必须提供一个原型和EventHandler<NewMailEventArgs>委托类型匹配的回调方法。

由于泛型System.EventHandler委托类型的定义如下:

public delegate void EventHandler<TEventArgs>(Object sender,TEventArgs e)
   where TEventArgs: EventArgs;

所以方法原型必须具有以下形式:

void MethodName(Object sender,NewMailEventArgs e);

 

11.1.3  第3步:定义负责引发事件的方法来通知事件的登记对象
根据约定,类应该定义一个受保护的虚方法。


要引发事件时,当前类及其派生类中的代码会调用该方法。该方法要获取一个参数,也就是NewMailEventArgs对象(包含给接收对象的信息)。该方法默认实现只检查是否有对象登记了对事件的关注。有就引发事件通知登记对象。

该方法在MailManager类中看起来像这样:

internal class MailManager{
  ...
  //第3步:定义一个负责引发事件的方法,它通知已登记的对象
  //事件已经发生。如果类似密封的,这个方法要声明为私有和非虚
  protected virtual void OnNewMail(NewMailEventArgs e){
    //出于线程安全的考虑,现在将对委托字段的引用复制到一个临时字段中
    EventHandler<EventArgs> temp =
       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<TEventArgs> temp =
         Interlocked.CompareExchage(ref eventDelegate,null,null);
        
       //任何方法登记了对我们的事件的关注,就通知它们
       if(temp!=null) temp(sender,e);       
    }
}


现在,可以像下面这样重新编写OnNewMail方法:

protected virtual void OnNewMail(NewMailEventArgs e){
   e.Raise(this,ref m_NewMail);
}

使用MailManager作为基类可自由重新OnNewMail方法。这个能力使派生类能控制事件的引发,从而以自己的方式处理新邮件。一般情况下,派生类会调用基类的OnNewMail方法,使登记的方法能收到通知。然而,派生类也可以不允许事件的转发。

 

11.1.4  第4步:定义方法将输入转化为期望事件

类还需要有一个方法来获取一些输入,并把它转化为事件的引发。
在MailManager的例子中,是调用SimulateNewMail方法来指出一封新的电子邮件已到达MailManager:
internal class MailManager{
  //第4步:定义方法,将输入转化为期望事件
  public void SimulateNewMail(string from,string to,string subject){
  //构造一个对象来容纳想传给通知接受者的信息
  NewMailEventArgs e = new NewMailEventArgs(from,to,subject);
 
  //调用虚方法通知对象事件已发生
  //如果没有类型重新该方法,我们的对象将通知事件的所有登记对象
  OnNewMail(e);
  }
}

SimulateNewMail接收一些关于邮件的信息,并构造一个NewMailEventArgs对象,将邮件信息传给它的构造器。然后调用MailManager自己的虚方法OnNewMail来正式通知MailManager对象收到了新的电子邮件。这通常会导致事件的引发,从而通知所有登记的方法。(如前所属,MailManager的派生类可能重写这个行为。)

 

11.2 编译器如何实现事件
MailManger类中,定义了事件成员本身:

public event EventHandler<NewMailEventArgs> NewMail;

C#编译时会转换为3个构造:
1.一个被初始化为null的私有委托字段
2.一个公告add_Xxx方法(Xxx是事件名)
3.一个公告remove_Xxx方法(Xxx是事件名)

 

11.3 设计侦听事件的类型
定义类型来使用另一个类型提供的事件。
Fax类型代码:

internal sealed class Fax{
   public Fax(MailManger mm){
   //构造EventHandler<NewMailEventArgs>委托的一个实例,
   //使它引用我们的FaxMsg回调方法,
   //向MailManager的NewMail事件登记我们的回调方法
   mm.NewMail+=FaxMsg;
   }

    //新邮件到达时,MailManager将调用这个方法
    private void FaxMsg(Object sender,NewMailEventArgs e){
    //'sender'表示MailManager对象,便于将信息传回给它
    //'e'表示MailManager对象想传给我们的附件事件信息
    
    //这里的代码正常情况下应该是传真邮件
    //但这个测试性的实现只是在控制台上显示邮件
    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;
    }
}


C#编译+=操作符为:

mm.add_NewMail(new EventHandler<NewMailEventArgs>(this.FaxMsg));

 

如果你的类型要实现IDisposable的Dispose方法,就应该在实现中注销对所有事件的关注。

C#编译-=操作符为:

mm.remove_NewMail(new EventHandler<NewMailEventArgs>(FaxMsg));

 

11.4 显式实现事件

System.Windows.Forms.Control类型定义了约70个事件。
大部分时候程序员只用到少数几个事件,所以从Control派生类创建的对象都浪费大量内存。

事件内部的实现:
首先实现的是EventSet类,它代表一个集合,其中包含了事件及每个事件的委托列表。

 

 

 

posted @ 2014-06-04 17:34  IT浪潮之巅  阅读(142)  评论(0编辑  收藏  举报
   友情链接: 淘宝优惠券