多事件的类型在实际应用中并不少见,尤其是在一些用户界面的类型中(例如在WindowsForm中的各种控件)。这些类型动辄将包含数十个事件,如果为每一个事件都添加一个事件成员,将导致无论使用者是否用到所有事件,每个类型对象都将占有很大的内存,那么对于系统的性能影响将不言而喻。事实上,.NET的开发小组运用了一种比较巧妙的方式来避免这一困境。.
Solution:当某个类型具有相对较多的事件时,我们可以考虑显示地设计订阅、取消订阅事件的方法,并且把所有的委托链表存储在一个集合之中。这样做就能避免在类型中定义大量的委托成员而导致类型过大。
下面通过一个具体的实例来说明这一设计:
① 定义包含大量事件的类型之一:使用EventHandlerList成员来存储所有事件
public partial class MultiEventClass
{
// EventHandlerList包含了一个委托链表的容器,实现了多事件存放在一个容器之中的包装,它使用的是链表数据结构
private EventHandlerList events;
public MultiEventClass()
{
// 初始化EventHandlerList
events = new EventHandlerList();
}
// 释放EventHandlerList
public void Dispose()
{
events.Dispose();
}
}
② 定义包含大量事件的类型之二:申明多个具体的事件
public partial class MultiEventClass
{
#region event1
// 事件1的委托原型
public delegate void Event1Handler(object sender, EventArgs e);
// 事件1的静态Key
protected static readonly object Event1Key = new object();
// 订阅事件和取消订阅
// 注意:EventHandlerList并不提供线程同步,所以加上线程同步属性
public event Event1Handler Event1
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
events.AddHandler(Event1Key, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
events.RemoveHandler(Event1Key, value);
}
}
// 触发事件1
protected virtual void OnEvent1(EventArgs e)
{
events[Event1Key].DynamicInvoke(this, e);
}
// 简单地触发事件1,以便于测试
public void RiseEvent1()
{
OnEvent1(EventArgs.Empty);
}
#endregion
#region event2
// 事件2的委托原型
public delegate void Event2Handler(object sender, EventArgs e);
// 事件2的静态Key
protected static readonly object Event2Key = new object();
// 订阅事件和取消订阅
// 注意:EventHandlerList并不提供线程同步,所以加上线程同步属性
public event Event2Handler Event2
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
events.AddHandler(Event2Key, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
events.RemoveHandler(Event2Key, value);
}
}
// 触发事件2
protected virtual void OnEvent2(EventArgs e)
{
events[Event2Key].DynamicInvoke(this, e);
}
// 简单地触发事件2,以便于测试
public void RiseEvent2()
{
OnEvent2(EventArgs.Empty);
}
#endregion
}
③ 定义事件的订阅者(它对多事件类型内部的构造一无所知)
public class Customer
{
public Customer(MultiEventClass events)
{
// 订阅事件1
events.Event1 += Event1Handler;
// 订阅事件2
events.Event2 += Event2Handler;
}
// 事件1的回调方法
private void Event1Handler(object sender, EventArgs e)
{
Console.WriteLine("事件1被触发");
}
// 事件2的回调方法
private void Event2Handler(object sender, EventArgs e)
{
Console.WriteLine("事件2被触发");
}
}
④ 编写入口方法来测试多事件的触发
public class Program
{
public static void Main(string[] args)
{
using(MultiEventClass mec = new MultiEventClass())
{
Customer customer = new Customer(mec);
mec.RiseEvent1();
mec.RiseEvent2();
}
Console.ReadKey();
}
}
最终运行结果如下图所示:
总结EventHandlerList的用法,在多事件类型中为每一个事件都定义了一套成员,包括事件的委托原型、事件的订阅和取消订阅方法,在实际应用中,可能需要定义事件专用的参数类型。这样的设计主旨在于改动包含多事件的类型,而订阅事件的客户并不会察觉这样的改动。设计本身不在于减少代码量,而在于有效减少多事件类型对象的大小。