如何设计一个带有很多事件的类型?
当某个类型包含较多的事件时,可以考虑集中把所有事件的委托链表存储在一个集合之中,这样做能有效地减少对象的大小,因为在实际逻辑世界中一个对象使用所有事件的概率相对很低。
.NET 的内建类型 System.ComponentModel.EventHandlerList
提供了这样一个存储事件集合的封装。
代码示例
1. 创建要给多事件的类 MultiEventsClass.cs
点击查看代码
csharp
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace MultiEvents
{
/// <summary>
/// 多事件类型
/// </summary>
public class MultiEventsClass : IDisposable
{
/// <summary>
/// System.ComponentModel.EventHandlerList 包含了一个委托链表的容器
/// 实现了多事件存放在一个容器之中的包装
/// EventHandlerList 使用的是链表数据结果
/// </summary>
private EventHandlerList _events;
/// <summary>
/// 构造函数
/// </summary>
public MultiEventsClass()
{
_events = new EventHandlerList();
}
// 申明事件 1
#region event1
// 事件 2 的委托原型
public delegate void Event1Handler(Object sender, EventArgs e);
// 静态字段,提高性能
protected static readonly Object Event1Key = new object();
/// <summary>
/// 一组订阅、取消订阅事件的方法
/// 注意 EventHandlerList 并不提供线程同步,
/// 所以在 add 和 remove 方法前加上线程同步属性
/// (可以采取 lock 机制来代替)
/// </summary>
public event Event1Handler Event1
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
_events.AddHandler(Event1Key, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
_events.RemoveHandler(Event1Key, value);
}
}
/// <summary>
/// 触发事件 1
/// </summary>
/// <param name="e"></param>
protected virtual void OnEvent1(EventArgs e)
{
_events[Event1Key].DynamicInvoke(this, e);
}
/// <summary>
/// 这个方法简单的触发事件 2,以便于测试
/// </summary>
public void RaiseEvent1()
{
OnEvent1(EventArgs.Empty);
}
#endregion
// 申明事件 2
#region Event2
// 事件 2 的委托原型
public delegate void Event2Handler(Object sender, EventArgs e);
// 静态字段,提高性能
protected static readonly Object Event2Key = new object();
/// <summary>
/// 一组订阅、取消订阅事件的方法
/// 注意 EventHandlerList 并不提供线程同步,
/// 所以在 add 和 remove 方法前加上线程同步属性
/// (可以采取 lock 机制来代替)
/// </summary>
public event Event2Handler Event2
{
[MethodImpl(MethodImplOptions.Synchronized)]
add
{
_events.AddHandler(Event2Key, value);
}
[MethodImpl(MethodImplOptions.Synchronized)]
remove
{
_events.RemoveHandler(Event2Key, value);
}
}
/// <summary>
/// 触发事件 2
/// </summary>
/// <param name="e"></param>
protected virtual void OnEvent2(EventArgs e)
{
_events[Event2Key].DynamicInvoke(this, e);
}
/// <summary>
/// 这个方法简单的触发事件 2,以便于测试
/// </summary>
public void RaiseEvent2()
{
OnEvent2(EventArgs.Empty);
}
#endregion
/// <summary>
/// 释放 EventHandlerList
/// </summary>
public void Dispose()
{
_events.Dispose();
}
}
}
2. 创建一个订阅事件的类型 Customer.cs
点击查看代码
csharp
using System;
namespace MultiEvents
{
/// <summary>
/// 构造一个订阅事件的类型
/// </summary>
public class Customer
{
public Customer(MultiEventsClass events)
{
// 订阅事件 1
events.Event1 += Event1Handler;
// 订阅事件 2
events.Event2 += Event2Handler;
}
/// <summary>
/// 事件 1 回调方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Event1Handler(object sender, EventArgs e)
{
Console.WriteLine("事件 1 触发");
}
/// <summary>
/// 事件 2 回调方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Event2Handler(object sender, EventArgs e)
{
Console.WriteLine("事件 2 触发");
}
}
}
3. 在入口方法中测试事件的触发
点击查看代码
csharp
using System;
namespace MultiEvents
{
class Program
{
static void Main(string[] args)
{
// 测试事件的触发
using (MultiEventsClass c = new MultiEventsClass())
{
Customer customer = new Customer(c);
c.RaiseEvent1(); // output:事件 1 触发
c.RaiseEvent2(); // output:事件 2 触发
}
Console.Read();
}
}
}
注意
代码存储事件集合用的是
EventHandlerList
类型,该类型的设计相当简单,即是一个链表数据结构的封装,并没有提供线程同步措施,设计时应考虑是否需要为 add 和 remove 方法添加线程同步机制。在多事件类型中,为每一个事件都定义了一套成员,包括事件的委托原型、事件的订阅和取消订阅方法,在实际应用中,可能需要定义事件专用的参数类型。
这样的设计旨在于改动包含多事件的类型,而订阅事件的客户并不会察觉这样的改动。
设计本身不在于减少代码量,而在于减少多事件类型对象的大小。