CodeGym /課程 /C# SELF /訂閱事件與建立處理器

訂閱事件與建立處理器

C# SELF
等級 52 , 課堂 2
開放

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: 資料夾中新檔案出現的通知。
  • 非同步操作: 檔案下載完成、任務進度更新。
  • 外掛系統: 不同模組可以訂閱主程式的事件。

事件模型讓架構可擴充:你可以新增模組並訂閱現有事件,而不需要改動基礎程式碼。

訂閱範例

事件簽名 訂閱範例 處理器範例
event Action
ev += Handler;
void Handler() { ... }
event Action<int>
ev += (x) => { ... };
void Handler(int x) { ... }
event EventHandler
ev += Handler;
void Handler(object s, EventArgs e) { ... }
event EventHandler<CustomArgs>
ev += Handler;
void Handler(object s, CustomArgs e) { ... }

5. 常見錯誤與陷阱

錯誤 #1:處理器簽名不匹配。
如果事件宣告為 event Action<int>,但你試圖訂閱一個無參數的方法,編譯器會報錯。一定要確認方法和事件的簽名一致。

錯誤 #2:lambda 捕獲變數。
用 lambda 訂閱時可能會捕獲外部變數(見「閉包」主題)。如果在訂閱後變數改變,處理器會看到新的值,這可能導致不可預期的行為。

錯誤 #3:在未初始化的物件上訂閱事件。
如果在包含事件的物件尚未建立或初始化前就訂閱它,可能會遇到 NullReferenceException。訂閱前先確保物件已可用。

錯誤 #4:同一處理器被重複訂閱。
如果同一個處理器被訂閱多次,它就會被呼叫相同次數。這不是 bug,而是事件行為,但常常會讓人意外。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION