C#中使用字典存储事件及自定义访问器实现
探讨在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();
}
}
执行上述代码后,控制台将依次输出:
2TestString
实现自定义事件访问器
事件本质上是特殊类型的多路广播委托,通常只能从声明它的类内部调用。客户端代码通过订阅方法引用来响应事件,这些引用通过事件的访问器添加到委托列表中。
在大多数标准场景下,开发者无需提供自定义的事件访问器,编译器会自动生成默认的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#高级事件处理的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













