您所在的位置: 首页>>读书频道>>设计开发>>其它开发>>

10.5 显式控制事件的订阅与注销

http://book.51cto.com  2008-02-13 14:44  Richter,J.著/周靖,张杰良 译  清华大学出版社  我要评论(0)
  • 摘要:《框架设计》(第2版)本书主要介绍了CLR Via C#的知识与技术。第十章讨论类型中可以定义的最后一种成员:事件。本文说的是显式控制事件的订阅与注销。
  • 标签:CLR  MailManager  C#  编译器  框架设计

10.5  显式控制事件的订阅与注销

有时我们会感到编译器生成的add和remove方法不是那么理想。例如10.4节中讨论的使用MicrosoftC#编译器时遇到的所有线程安全问题。实际上,Microsoft的C#编译器在安全编程(defensive coding)和健壮性方面永远不是最安全的。为了创建一个坚固的组件,建议经常采用本节介绍的技术,该技术可以用于解决与线程安全相关的所有问题。而且该技术同样也可以应用于其他目的。例如,显式实现add和remove方法的普遍原因就是类型定义了许多事件,而且又需要高效地进行存储。有关详情请参见10.6节。

幸亏C#编译器以及其他许多编译器都允许开发人员显式地实现add和remove访问器方法。为了保证MailManager对象上事件的订阅和注销的线程安全,我们修改了MailManager类的代码,修改后的代码如下所示:

internal class MailManager {

    //创建一个作为线程同步锁的私有实例字段
    private readonly Object m_eventLock = new Object();

    //增加一个引用委托链表头部的私有字段
    private EventHandler m_NewMail;

    //为类增加一个事件成员
    public event EventHandler NewMail {
        //显式实现'add'方法
        add {
            //加私有锁,并向委托链表增加一个处理程序(以'value'为参数)
            lock (m_eventLock) { m_NewMail += value; } 
        }

        //显式实现'remove'方法
        remove {
            //加私有锁,并从委托链表从中移除处理程序(以'value'为参数)
            lock (m_eventLock) { m_NewMail -= value; }
        }
    }


    //第三步:定义一个负责引发事件的方法,来通知已订阅事件的对象事件已经发生。如果类是封装的,
    //则需要将方法声明为private和non-virtual
    protected virtual void OnNewMail(NewMailEventArgs e) {

        //出于线程安全的考虑,将委托字段保存到一个临时字段中
        EventHandler temp = m_NewMail;

        //通知所有已订阅事件的对象
        if (temp != null) temp(this, e);
    }

    //第四步:定义一个方法,将输入转化为期望事件
    public void SimulateNewMail(String from, String to, String subject) {

        //构建一个对象来存放我们希望传递给通知接收者的信息
        NewMailEventArgs e = new NewMailEventArgs(from, to, subject);

        //调用虚方法以通知对象事件已经发生。如果没有对象重写该方法,那么该对象将通知所有订阅
        //该事件的对象
        OnNewMail(e);
    }
}

在新版的MailManager中,必须显式地定义引用委托链表的私有字段m_NewMail。在原来的事件语法中,C#编译器自动地将字段定义为private。在使用新版的事件语法时,开发人员在显式提供add和remove访问器方法实现时,必须同样显式地声明字段。

m_NewMail字段只是一个EventHandler<NewMailEventArgs>委托的引用。该字段还不能成为一个事件。关键字event后新的扩展事件语法实际上就是类型中定义事件的内容。add和remove块中的代码提供了访问器方法的实现。注意,每个方法都接受一个称为value的隐藏参数,该参数的类型为EventHandler<NewMailEventArgs>。方法内部,方法或者实现向委托链表中增加一个委托所需的代码,或者从委托链表中移除一个委托所需的代码。和属性不同,属性可以有一个get访问器方法,也可以有一个set访问器方法,还可以同时拥有这两个访问器方法,但是事件必须同时拥有add和remove访问器方法。

以上示例中的显式实现除了省略 [MethodImpl(MethodImplOptions. Synchronized)]特性之外,其他地方和C#编译器为方法提供的实现拥有相同的行为,相反,C#的lock语句和一个被私下定义为Object (m_eventLock)对象的引用一起使用。这就是在10.4节提到的修正线程安全问题的方法。由于m_eventLock字段被声明为private,所以MailManager类外面的代码无法访问这个字段,这将使MailManager类更加健壮。

事件可以被声明为static成员,这将使add和remove访问器方法也被声明为static。当然,私有字段m_NewMail也变为static。然后,为了获得正确的线程安全,m_eventLock字段也应声明为static。对于希望以线程安全的方式对外提供静态事件的引用类型或者值类型来说,这种方法是可行的。然而,正如10.4节所述,因为没有好的方法初始化值类型中的实例字段(具体原因请参见第5章),所以也没有好的方法保证值类型实例事件的线程安全。

实现事件订阅或者注销的代码不能判断事件的add和remove方法是由编译器自动创建的还是由编程人员显式实现的。实际上,订阅和注销事件的代码仍然可以使用+=和-=操作符,而且编译器在遇到这两个操作符时会自动地生成显式定义方法的调用。

最后需要指出的是OnNewMail方法。从语义上讲,这个OnNewMail方法与前一版本中OnNewMail方法相同。两者惟一的差别就是事件的名称(NewMail)被替换为委托字段的名称(m_NewMail)。

【责任编辑:董书 TEL:(010)68476606】

回书目   上一节   下一节
C#实用基础教程
C#技术开发指南
深入Vista应用程序开发
Ruby on Rails 社区网站开发
ASP.NET从入门到精通
 
 验证码: (点击刷新验证码)   匿名发表
  • Visual C++ 完全自学宝典

  • 作者:强锋科技,朱洪波
  • Visual C++ 6.0是微软公司为程序人员提供的Visual Studio 6.0工具套件中的重要组成部分。本书由浅入深地介绍使用Visual C++ 6.0..
Copyright©2005-2008 51CTO.COM 版权所有