C#如何设计一个带有多个事件的类型?

多事件的类型在实际应用中并不少见,尤其是在一些用户界面的类型中(例如在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();
    }
}

最终运行结果如下图所示:

C#如何设计一个带有多个事件的类型?

总结EventHandlerList的用法,在多事件类型中为每一个事件都定义了一套成员,包括事件的委托原型、事件的订阅和取消订阅方法,在实际应用中,可能需要定义事件专用的参数类型。这样的设计主旨在于改动包含多事件的类型,而订阅事件的客户并不会察觉这样的改动。设计本身不在于减少代码量,而在于有效减少多事件类型对象的大小。