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 |
在新版的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)。
| 回书目 上一节 下一节 |