1. 訂閱事件的語法
想像一個任務管理應用:當任務完成時會觸發事件,處理器可以去發送通知、更新 UI 或寫入日誌。C# 讓事件訂閱變得簡單又安全:你明確指出哪個處理器會響應哪個事件。
在 C# 裡訂閱事件就像在派對上把自己加到來賓名單:
publisher.MyEvent += HandlerMethod;
這裡 publisher 是宣告事件 MyEvent 的物件,HandlerMethod 是在事件發生時會被呼叫的方法。
來看一個最小範例。假設我們有一個計數點擊的應用:
public class Clicker
{
public event Action Clicked;
public void Click()
{
// 某東西被點擊了!
Clicked?.Invoke();
}
}
這就是我們的事件發佈者 Clicked。現在來訂閱一個處理器:
Clicker clicker = new Clicker();
void OnClicked()
{
Console.WriteLine("按鈕被按下了!");
}
clicker.Clicked += OnClicked;
// 在程式某處
clicker.Click();
// → "按鈕被按下了!"
它如何運作?我們把方法 OnClicked 加到「點擊」事件上,當發生點擊時它就會被呼叫。
2. 事件處理器:種類與如何宣告
事件處理器是事件觸發時會被呼叫的方法。它的簽名必須與事件所使用的 delegate 類型一致。例如,若事件宣告為 public event Action Clicked;, 那麼處理器就必須是無參數且無回傳值的方法。
針對 Action 的處理器
void OnClicked()
{
Console.WriteLine("事件發生了 (Action)!");
}
針對標準的 EventHandler 的處理器
使用傳統的 EventHandler 時,處理器會接受兩個參數:發送者(object sender)和事件資料(EventArgs e):
public class Alarm
{
public event EventHandler AlarmRaised;
public void RaiseAlarm()
{
AlarmRaised?.Invoke(this, EventArgs.Empty);
}
}
Alarm alarm = new Alarm();
void AlarmHandler(object sender, EventArgs e)
{
Console.WriteLine("警報已觸發!");
}
alarm.AlarmRaised += AlarmHandler;
alarm.RaiseAlarm();
匿名方法與 lambda 表達式
C# 允許在訂閱時直接使用匿名函式和 lambda:
clicker.Clicked += () => Console.WriteLine("Another click!");
如果事件帶參數,也可以這樣寫:
alarm.AlarmRaised += (sender, e) =>
{
Console.WriteLine($"Alarm raised by: {sender}");
};
4. 有用的小細節
訂閱與取消訂閱:重要的細節
現實生活中派對人太多或有人要離開是常有的事。對委派也是一樣:你可以加入(訂閱)或移除(取消訂閱)處理器:
// 訂閱
publisher.MyEvent += MyHandler;
// 取消訂閱(當處理器不再需要時)
publisher.MyEvent -= MyHandler;
為什麼重要?如果不取消訂閱,尤其在大型應用中,處理器可能會一直掛著,阻止 GC 回收物件——這會導致記憶體洩漏。
多個處理器
一個事件可以訂閱任意多個處理器。它們會按照加入的順序依序被呼叫。
clicker.Clicked += () => Console.WriteLine("第一個處理器!");
clicker.Clicked += () => Console.WriteLine("第二個處理器!");
clicker.Click();
// → 第一個處理器!
// → 第二個處理器!
你甚至可以在執行中即時訂閱或取消訂閱,這很靈活。
為什麼這真的很重要:實際場景
- UI (Windows Forms, WPF, WinUI, MAUI): 按鈕點擊、文字框改變 — 這些都是事件。
- FileSystemWatcher: 資料夾中新檔案出現的通知。
- 非同步操作: 檔案下載完成、任務進度更新。
- 外掛系統: 不同模組可以訂閱主程式的事件。
事件模型讓架構可擴充:你可以新增模組並訂閱現有事件,而不需要改動基礎程式碼。
訂閱範例
| 事件簽名 | 訂閱範例 | 處理器範例 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
5. 常見錯誤與陷阱
錯誤 #1:處理器簽名不匹配。
如果事件宣告為 event Action<int>,但你試圖訂閱一個無參數的方法,編譯器會報錯。一定要確認方法和事件的簽名一致。
錯誤 #2:lambda 捕獲變數。
用 lambda 訂閱時可能會捕獲外部變數(見「閉包」主題)。如果在訂閱後變數改變,處理器會看到新的值,這可能導致不可預期的行為。
錯誤 #3:在未初始化的物件上訂閱事件。
如果在包含事件的物件尚未建立或初始化前就訂閱它,可能會遇到 NullReferenceException。訂閱前先確保物件已可用。
錯誤 #4:同一處理器被重複訂閱。
如果同一個處理器被訂閱多次,它就會被呼叫相同次數。這不是 bug,而是事件行為,但常常會讓人意外。
GO TO FULL VERSION