Skip to content

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

🏷️ C# 学习

当某个类型包含较多的事件时,可以考虑集中把所有事件的委托链表存储在一个集合之中,这样做能有效地减少对象的大小,因为在实际逻辑世界中一个对象使用所有事件的概率相对很低。

.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();
        }
    }
}

注意

  1. 代码存储事件集合用的是 EventHandlerList 类型,该类型的设计相当简单,即是一个链表数据结构的封装,并没有提供线程同步措施,设计时应考虑是否需要为 add 和 remove 方法添加线程同步机制。

  2. 在多事件类型中,为每一个事件都定义了一套成员,包括事件的委托原型、事件的订阅和取消订阅方法,在实际应用中,可能需要定义事件专用的参数类型。

  3. 这样的设计旨在于改动包含多事件的类型,而订阅事件的客户并不会察觉这样的改动。

  4. 设计本身不在于减少代码量,而在于减少多事件类型对象的大小。