定义一个事件成员意味着类型具有三种能力:
*类型的静态方法/实例方法可以订阅类型事件
*类型的静态方法/实例方法可以注销类型事件
*事件发生时通知已订阅事件的方法
.NET2.0的事件仍然是基于Win32的,只不过使用了Observer模式来实现,同时建立在Delegate机制之上。
事件的设计步骤如下(基本上是Observer的实现步骤):
10.1 设计一个对外提供事件的类型
1.定义EventArgs或子类,用于存放附加信息:
定义一个类,继承于EventArgs,以EventArgs结束,包含一组私有字段以及相应的只读公共属性。
public class NewMailEventArgs : EventArgs
{
private string from;
public string From
{
get { return from; }
}
}
{
private string from;
public string From
{
get { return from; }
}
}
这里,EventArgs基类在FCL中是这个样子的:
[Serializable]
[ComVisible(true)]
public class EventArgs
{
// Summary:
// 表示没有事件数据的事件。
public static readonly EventArgs Empty;
public EventArgs();
}
[ComVisible(true)]
public class EventArgs
{
// Summary:
// 表示没有事件数据的事件。
public static readonly EventArgs Empty;
public EventArgs();
}
大多数事件没有附加数据,那么就不用定义任何私有字段和属性,直接使用EventArgs基类作为参数。
2.定义事件成员:




这条语句等价于:

所以方法原型相应为 void MethodName(Object sender, NewMailEventArgs e)
这里,第一个参数sender类型是Object,因为要兼容所有类型,所以提供一个最广泛的基类型。
第二个参数名始终是e,而且派生于EventArgs,保持了对Observer模式的一致性,所有人(包括VS2005)都会调用这个e
事件方法要求都为void,即不允许有回调值,从而事件链易于操作。
3.定义引发事件的方法——负责通知订阅事件的对象:
这是一个protected的虚方法,并接受EventArgs或其子类的参数。
这个虚方法可以由派生类重写,以添加新的功能;不重写也可以,因为基本上已经可以使用了










这里,使用临时变量temp,是为了防止可能存在的线程同步问题。
4.定义一个激发事件的方法
将输入转换成EventArgs或其子类的对象,然后激发事件








10.3 设计订阅者的类,使用事件
在ctor中订阅事件,绑定FaxMsg回调方法,在Unregister方法中注销事件
提供回调方法FaxMsg,当事件激发时自动调用

















注意:使用+=和-=操作符,而不能显示使用add/remove方法
事件注销的意义:只要有一个对象还有一个方法仍然订阅事件,该对象就不会被垃圾收集
IDispose接口的Dispose方法,注销所有事件。
FaxMsg方法的sender参数为MailMessager对象,可以使用sender访问MailMessager的对象成员,
补充:在Main函数中实现:

















10.2 事件机制
对于public event EventHandler<NewMailEventArgs> NewMail;
C#编译时,相应为
















注: 在IL中也是3个成员:一个私有字段,两个公有方法
如果将event声明为protected,则两个方法也相应为protected
event也可以是static或virtual,则两个方法也相应为static或virtual
10.4 事件与线程安全
在上面的实例中,System.Runtime.CompilerServices命名空间下,自定义属性[MethodImpl(MethodImplOptions.Synchronized)]保证了事件的线程同步。
但是这样的同步会有问题。
对于实例事件,CLR使用自身对象作为线程同步锁;
对于静态事件,CLR使用类型对象作为线程同步锁。
但是线程同步指导方针指出,方法永远不要在对象本身或类型对象上加锁,否则这个锁对外公开,会导致其它线程死锁
没有好的办法保证值类型的实例事件成员是线程安全的,因为C#不会为其add/remove生成[MethodImpl(MethodImplOptions.Synchronized)];
值类型的静态事件成员肯定是线程安全的。
10.5 显示控制事件的订阅与注销
即显示的实现add和remove访问器方法:
建立一个临时委托变量m_NewMail与相应的属性,代替原先的事件成员NewMail,
新建一个作为线程同步锁的私有实例字段m_eventLock
主要改动如下:























注意,C#不能分辨add/remove方法是由编译器自动创建的,还是程序员显示实现的,所以仍可以使用+=和-=这两个操作符处理事件。
10.6 多事件模型
System.Windows.Forms.Control类型有70多个事件,不可能用上述方法实现,会造成未使用事件对内存的浪费。
解决办法:使用注册工厂,建立事件池。具体见设计模式。