當類別擁有大量事件但預期大多數不會被訂閱時,為每個事件宣告欄位會浪費記憶體。使用 Dictionary<string, Delegate> 集中儲存可大幅減少記憶體佔用,這是 Component 類別的核心設計模式。
public delegate void EventHandler1(int i);
public delegate void EventHandler2(string s);
public class PropertyEventsSample
{
// 事件表:鍵為事件名稱,值為委派鏈
private Dictionary<string, Delegate> eventTable;
public PropertyEventsSample()
{
eventTable = new Dictionary<string, Delegate>();
eventTable.Add("Event1", null);
eventTable.Add("Event2", null);
}
// Event1 自定義 add/remove 存取器
public event EventHandler1 Event1
{
add
{
lock (eventTable)
{
eventTable["Event1"] = (EventHandler1)eventTable["Event1"] + value;
}
}
remove
{
lock (eventTable)
{
eventTable["Event1"] = (EventHandler1)eventTable["Event1"] - value;
}
}
}
// Event2 同理
public event EventHandler2 Event2
{
add
{
lock (eventTable)
{
eventTable["Event2"] = (EventHandler2)eventTable["Event2"] + value;
}
}
remove
{
lock (eventTable)
{
eventTable["Event2"] = (EventHandler2)eventTable["Event2"] - value;
}
}
}
// 引發事件的受保護方法
protected void RaiseEvent1(int i)
{
if (eventTable["Event1"] is EventHandler1 handler)
handler(i);
}
protected void RaiseEvent2(string s)
{
if (eventTable["Event2"] is EventHandler2 handler)
handler(s);
}
}
var p = new PropertyEventsSample();
p.Event1 += new EventHandler1(TestClass.Delegate1Method);
p.Event1 += new EventHandler1(TestClass.Delegate1Method); // 重複訂閱
p.Event1 -= new EventHandler1(TestClass.Delegate1Method); // 取消一次
p.RaiseEvent1(2); // 僅觸發一次,輸出: 2
p.Event2 += new EventHandler2(TestClass.Delegate2Method);
p.Event2 += new EventHandler2(TestClass.Delegate2Method);
p.Event2 -= new EventHandler2(TestClass.Delegate2Method);
p.RaiseEvent2("TestString"); // 輸出: TestString
當類別實作多個介面且包含同名事件時,需使用顯式介面實作並自定義 add/remove:
public interface IDrawingObject
{
event EventHandler OnDraw; // 繪製前引發
}
public interface IShape
{
event EventHandler OnDraw; // 繪製後引發
}
public class Shape : IDrawingObject, IShape
{
// 內部兩個獨立事件
private event EventHandler PreDrawEvent;
private event EventHandler PostDrawEvent;
private readonly object lockObj = new object();
// 顯式實作 IDrawingObject.OnDraw → 對應 PreDrawEvent
event EventHandler IDrawingObject.OnDraw
{
add
{
lock (lockObj) PreDrawEvent += value;
}
remove
{
lock (lockObj) PreDrawEvent -= value;
}
}
// 顯式實作 IShape.OnDraw → 對應 PostDrawEvent
event EventHandler IShape.OnDraw
{
add
{
lock (lockObj) PostDrawEvent += value;
}
remove
{
lock (lockObj) PostDrawEvent -= value;
}
}
public void Draw()
{
// 繪製前事件
PreDrawEvent?.Invoke(this, EventArgs.Empty);
Console.WriteLine("Drawing a shape.");
// 繪製後事件
PostDrawEvent?.Invoke(this, EventArgs.Empty);
}
}
var shape = new Shape();
// 訂閱 IDrawingObject.OnDraw (繪製前)
IDrawingObject d = shape;
d.OnDraw += (s, e) => Console.WriteLine("Sub1: 繪製前事件");
// 訂閱 IShape.OnDraw (繪製後)
IShape s = shape;
s.OnDraw += (s, e) => Console.WriteLine("Sub2: 繪製後事件");
shape.Draw();
// 輸出:
// Sub1: 繪製前事件
// Drawing a shape.
// Sub2: 繪製後事件
| 場景 | 解決方案 |
|---|---|
| 大量稀疏事件 | Dictionary<string, Delegate> + 自定義 add/remove |
| 多介面同名事件 | 顯式介面實作 + 內部獨立事件欄位 |
| 執行緒安全 | lock 保護字典/事件欄位操作 |
| 引發事件 | 建立暫存委派副本避免競態條件 |
.NET 4.5+ 可考慮使用 EventHandlerList(Component 內部即使用此類別),它針對稀疏事件優化,提供 AddHandler/RemoveHandler 方法,比手動管理 Dictionary 更完善。