C#中使用字典存储事件及自定义访问器实现

今天早睡
今天早睡
发布时间:2026-10-13 | 2人浏览过
原创

探讨在C#中利用字典存储事件实例的高级用法,以及通过自定义add/remove访问器控制事件订阅行为的方法,帮助开发者在复杂场景下优化内存占用并保障线程安全。

使用字典存储多个事件

在C#编程中,accessor-declarations(访问器声明)的一种常见应用是当类需要公开大量事件时,为了避免为每个事件单独分配一个字段从而占用过多内存,可以使用字典来集中存储这些事件实例。这种模式特别适用于预期大多数事件都不会被实际订阅的场景。

实现的核心思路是将事件名作为键,委托类型作为值存储在字典中。以下是一个完整的示例,展示了如何定义两个不同类型的事件,并通过字典进行管理和触发:

public delegate void EventHandler1(int i);
public delegate void EventHandler2(string s);

public class PropertyEventsSample
{
  // 使用字典存储事件实例
  private System.Collections.Generic.Dictionary<string, System.Delegate> eventTable;

  public PropertyEventsSample()
  {
    eventTable = new System.Collections.Generic.Dictionary<string, System.Delegate>();
    // 预注册事件键
    eventTable.Add("Event1", null);
    eventTable.Add("Event2", null);
  }

  // 第一个事件的自定义访问器
  public event EventHandler1 Event1
  {
    add
    {
      lock (eventTable)
      {
        eventTable["Event1"] = (EventHandler1)eventTable["Event1"] + value;
      }
    }
    remove
    {
      lock (eventTable)
      {
        eventTable["Event1"] = (EventHandler1)eventTable["Event1"] - value;
      }
    }
  }

  // 第二个事件的自定义访问器
  public event EventHandler2 Event2
  {
    add
    {
      lock (eventTable)
      {
        eventTable["Event2"] = (EventHandler2)eventTable["Event2"] + value;
      }
    }
    remove
    {
      lock (eventTable)
      {
        eventTable["Event2"] = (EventHandler2)eventTable["Event2"] - value;
      }
    }
  }

  // 触发Event1
  internal void RaiseEvent1(int i)
  {
    EventHandler1 handler1;
    if (null != (handler1 = (EventHandler1)eventTable["Event1"]))
    {
      handler1(i);
    }
  }

  // 触发Event2
  internal void RaiseEvent2(string s)
  {
    EventHandler2 handler2;
    if (null != (handler2 = (EventHandler2)eventTable["Event2"]))
    {
      handler2(s);
    }
  }
}

public class TestClass
{
  public static void Delegate1Method(int i)
  {
    System.Console.WriteLine(i);
  }

  public static void Delegate2Method(string s)
  {
    System.Console.WriteLine(s);
  }

  static void Main()
  {
    PropertyEventsSample p = new PropertyEventsSample();

    // 订阅事件1
    p.Event1 += new EventHandler1(TestClass.Delegate1Method);
    p.Event1 += new EventHandler1(TestClass.Delegate1Method);
    // 移除一次订阅
    p.Event1 -= new EventHandler1(TestClass.Delegate1Method);
    p.RaiseEvent1(2); // 输出: 2

    // 订阅事件2
    p.Event2 += new EventHandler2(TestClass.Delegate2Method);
    p.Event2 += new EventHandler2(TestClass.Delegate2Method);
    // 移除一次订阅
    p.Event2 -= new EventHandler2(TestClass.Delegate2Method);
    p.RaiseEvent2("TestString"); // 输出: TestString

    System.Console.WriteLine("Press any key to exit.");
    System.Console.ReadKey();
  }
}

执行上述代码后,控制台将依次输出:

  • 2
  • TestString

实现自定义事件访问器

事件本质上是特殊类型的多路广播委托,通常只能从声明它的类内部调用。客户端代码通过订阅方法引用来响应事件,这些引用通过事件的访问器添加到委托列表中。

在大多数标准场景下,开发者无需提供自定义的事件访问器,编译器会自动生成默认的add和remove逻辑。然而,在某些特定需求下(如需要改变订阅规则、记录日志或强制同步),可能需要重写这些访问器。

第1步:定义锁定对象

为了保证多线程环境下的线程安全,建议在添加或移除事件处理程序之前对相关的资源进行加锁。例如,如果底层使用了一个私有委托字段PreDrawEvent作为存储,则需要一个独立的锁定对象。

第2步:实现Add访问器

在add块中,首先获取锁,然后将传入的值(value)累加到底层委托上。这确保了多个线程同时尝试订阅时不会引发竞争条件。

第3步:实现Remove访问器

类似地,在remove块中获取锁并从底层委托中移除指定的处理程序。虽然可以替换访问器内的任何逻辑代码,但建议保留锁定机制以防止数据损坏。

以下是自定义访问器的标准模板示例:

private event EventHandler PreDrawEvent;

public event EventHandler OnDraw
{
  add
  {
    lock (PreDrawEvent)
    {
      PreDrawEvent += value;
    }
  }
  remove
  {
    lock (PreDrawEvent)
    {
      PreDrawEvent -= value;
    }
  }
}

通过上述方式,开发者可以在保持接口兼容性的同时,灵活控制事件订阅的行为和安全性。

以上就是C#中使用字典存储事件及自定义访问器实现的详细内容,更多关于C#高级事件处理的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表