第 11 章 事 件

第 11 章 事 件

本章内容:

11.1 设计要公开事件的类型

11.1.1 第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息

11.1.2 第二步:定义事件成员

11.1.3 第三步:定义负责引发事件的方法来通知事件的登记对象

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System;
using System.Threading;

namespace ch11
{
// 第一步:定义一个类型来容纳所有应该发送给事件通知接受者的附加信息
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; }
}
}

internal class MailManager
{
// 第二部:定义事件成员
public event EventHandler<NewMailEventArgs> NewMail;

// 第三步 : 定义负责引发事件的方法来通知已登记的对象。
// 如果类是密封的,该方法要声明为私有和非虚
protected virtual void OnNewMail(NewMailEventArgs e)
{
// 出于线程安全的考虑,现在将对委托字段的引用复制到一个临时变量中
EventHandler<NewMailEventArgs> temp = Volatile.Read(ref NewMail);

// 任何方法登记了对事件的关注,就通知它们
if (temp != null) temp(this, e);

// 可以像下面这样重写 OnNewMail 方法
// e.Raise(this, ref NewMail);
}

// 第四步:定义方法将输入转化为期望事件
public void SimulateNewNail(String from, String to, String subject)
{
// 构造一个对象来容纳想传给通知接收者的信息
NewMailEventArgs e = new NewMailEventArgs(from, to, subject);

// 调用虚方法通知对象事件已发生,
// 如果没有类型重写该方法,我们的对象将通知事件的所有登记对象
OnNewMail(e);
}
}

public static class EventArgsExtensions
{
public static void Raise<TEventArgs>(this TEventArgs e, Object sender,
ref EventHandler<TEventArgs> eventDelegate)
{
// 出于线程安全的考虑,现在将对委托字段的引用复制到临时
EventHandler<TEventArgs> temp = Volatile.Read(ref eventDelegate);

// 任何方法登记了对事件的关注就通知它们
if (temp != null) temp(sender, e);
}
}
}

11.2 编译器如何实现事件

MailManager类用一行代码定义了事件成员本身:
public event EventHandler<NewMailEventArgs> NewMail;

C# 编译器编译时把它转换为以下 3 个构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 1. 一个初始化为 null 的私有委托字段
private EventHandler<NewMailEventArgs> NewMail = null;

// 2. 一个公共 add_Xxx 方法(其中 XXX 是事件名)
// 允许方法登记对事件的关注
public void add_NewMail(EventHandler<NewMailEventArgs> value) {
// 通过循环和对 CompareExchange 的调用,可以
// 以一种线程安全的方式向事件添加委托
EventHandler<NewMailEventArgs> preHandler;
EventHandler<NewMailEventArgs> newMail = this.NewMail;
do {
preHandler = newMail;
EventHandler<NewMailEventArgs> newMail = (EventHandler<NewMailEventArgs>) Delegate.Combine(preHandler, value);
newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, preHandler);
} while (newMail != preHandler);
}

// 3. 一个公共 remove_Xxx 方法(其中Xxx是事件名)
// 允许方法注销对事件的关注
public void remove_NewMail(EventHandler<NewMailEventArgs> value) {
// 通过循环和对 CompareExchange 的调用,可以
// 以一种线程安全的方式从事件中移除一个委托
EventHandler<NewMailEventArgs> preHandler;
EventHandler<NewMailEventArgs> newMail = this.NewMail;
do {
preHandler = newMail;
EventHandler<NewMailEventArgs> newHandler = (EventHandler<NewMailEventArgs>) Delegate.Remove(preHandler, value);
newMail = Interlocked.CompareExchange<EventHandler<NewMailEventArgs>>(ref this.NewMail, newHandler, preHandler);
} while (newMail != preHandler);
}

11.3 设计侦听事件的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
internal sealed class Fax {
// 将 MailManager 对象传给构造器
public Fax(MailManager 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;
}
}

11.4 显式实现事件

通过显式实现事件,开发人员可以控制add和remove方法处理回调委托的方式,以高效地处理大量事件。为了存储事件委托,每个对象都维护一个集合,使用事件标识符作为键,委托列表作为值。当对象需要触发事件时,将调用与标识符关联的委托列表。具体实现方式由定义事件的类型的开发人员决定。