適用場景:GUI交互、消息隊列、微服務通信等需要解耦事件生產與消費的系統
🧩 模式核心組件解析
發布者(Publisher)
- 作用:定義事件并管理訂閱者列表
- 關鍵行為:
- 提供+=和-=運算符注冊/注銷訂閱者
- 通過Invoke()方法觸發事件 (圖示15-3)
- 代碼示例:
public class Incrementer {public event EventHandler CountedADozen; // 定義事件 public void DoCounting() {for (int i=1; i<=100; i++) {if (i % 12 == 0) CountedADozen?.Invoke(this, EventArgs.Empty); // 觸發事件 }}
}
訂閱者(Subscriber)
- 職責:向發布者注冊事件處理方法
- 設計原則:
- 方法需匹配委托簽名(參數+返回值)
- 避免長時間阻塞(影響其他訂閱者)
注冊示例
public class Dozens {public void Subscribe(Incrementer publisher) {publisher.CountedADozen += OnCountedDozen; // 注冊事件 }private void OnCountedDozen(object sender, EventArgs e) {Console.WriteLine("12的倍數已累計!");}
}
事件與委托的共生關系(關鍵!)
- 事件本質:對委托的封裝(圖示15-2)
- 封裝價值
- 安全優勢:阻止外部重置訂閱列表,保障事件觸發的確定性
?? 實戰設計要點
事件參數自定義
public class CountEventArgs : EventArgs {public int CurrentCount { get; set; } // 傳遞計數狀態
}
// 發布者觸發時傳遞參數
CountedADozen?.Invoke(this, new CountEventArgs { CurrentCount = i });
多線程環境安全
// 使用原子操作避免線程競爭
EventHandler handler = CountedADozen;
if (handler != null) {handler(this, EventArgs.Empty);
}
解耦進階方案
? 避坑指南
內存泄漏風險
- 問題:訂閱者未注銷 → 阻止GC回收對象
- 方案:在Dispose()中執行publisher.Event -= handler
事件處理異常傳播
- 陷阱:某個訂閱者拋異常 → 中斷后續執行
- 方案:獨立捕獲每個處理器的異常
foreach (var handler in CountedADozen.GetInvocationList()) {try { handler.DynamicInvoke(args); } catch (Exception ex) { /* 記錄日志 */ }
}
技術啟示:事件驅動架構通過解耦生產者與消費者,顯著提升系統擴展性。掌握其底層委托機制,可避免“僅會用不懂原理”的窘境。建議結合Rx.NET或MediatR庫深化實戰能力。