事件是委派的封裝,實現「訂閱-發布」模式。類別定義事件,外部物件訂閱 (加入處理器),觸發條件滿足時類別發布 (引發事件)。
using System;
class Counter
{
private int _count;
public delegate void CountChangedHandler(int newCount); // 委派型別
public event CountChangedHandler CountChanged; // 事件
public int Count
{
get => _count;
set
{
if (_count != value)
{
_count = value;
CountChanged?.Invoke(_count); // 引發事件 (C# 6 null 條件運算子)
}
}
}
}
class Program
{
static void Main()
{
var c = new Counter();
c.CountChanged += OnCountChanged; // 訂閱
c.Count = 5; // 輸出: Count changed to 5
c.Count = 10; // 輸出: Count changed to 10
}
static void OnCountChanged(int n) => Console.WriteLine($"Count changed to {n}");
}
| 語法 | 說明 |
|---|---|
| 定義事件處理器的簽章 (回傳型別通常為 void)。 | |
關鍵字聲明事件,限制外部只能 +=/-=,不能直接賦值或 Invoke。 |
|
| 訂閱 (加入多播委派調用清單)。 | |
| 取消訂閱 (移除)。 | |
| 安全引發 (無訂閱者時不拋 NullReferenceException)。 |
.NET Framework 設計指南建議:
public class CustomEventArgs : EventArgs
{
public string Message { get; }
public CustomEventArgs(string msg) => Message = msg;
}
public class Publisher
{
// 1. 事件委派 (慣用 EventHandler<TEventArgs>)
public event EventHandler<CustomEventArgs> CustomEvent;
// 2. 受保護虛擬方法 (衍生類可覆寫引發邏輯)
protected virtual void OnCustomEvent(CustomEventArgs e)
{
CustomEvent?.Invoke(this, e); // sender 為 this
}
// 3. 業務方法觸發
public void DoSomething(string msg)
{
OnCustomEvent(new CustomEventArgs(msg));
}
}
private EventHandler<CustomEventArgs> _customEvent;
public event EventHandler<CustomEventArgs> CustomEvent
{
add
{
lock (_lock) _customEvent += value; // 執行緒安全訂閱
}
remove
{
lock (_lock) _customEvent -= value;
}
}
防止訂閱者被事件持有者根引用導致記憶體洩漏。使用 WeakReference 或 WPF 的 WeakEventManager。
public event Func AsyncEvent;
protected virtual async Task OnAsyncEventAsync(CustomEventArgs e)
{
var handlers = AsyncEvent?.GetInvocationList();
if (handlers != null)
{
var tasks = handlers.Select(h => ((Func)h)(e));
await Task.WhenAll(tasks);
}
}
| 陷阱 | 解決方案 |
|---|---|
| 忘記取消訂閱導致記憶體洩漏 | 實作 IDisposable 在 Dispose 中 -=;或使用弱事件。 |
| 事件處理器拋出例外中斷後續處理器 | 手動遍歷 GetInvocationList() 配合 try-catch。 |
| 多執行緒競態 (訂閱/引發同時進行) | 複製委派快照 var handler = Event; handler?.Invoke(...) 或自訂存取器加鎖。 |
| 在建構函式中訂閱靜態事件 | 極易洩漏;改用工廠方法或延遲訂閱。 |
事件是 C# 實現鬆耦合通知機制的核心。遵循標準模式 (EventHandler<T>、OnXxx 虛擬方法)、注意執行緒安全與生命週期管理,能寫出健壯且可維護的事件驅動程式碼。