CodeGym /課程 /C# SELF /標準事件範本 ( EventHandler

標準事件範本 ( EventHandler/ EventArgs)

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

1. 介紹

回想一下:事件是一個公開的契約,承諾你的程式在某件重要事情發生時會呼叫訂閱者。在之前的課程範例裡你可能看到過這種宣告:


public event Action<string> MessageSent;

或者甚至:


public delegate void MyHandler(int value);
public event MyHandler SomethingHappened;

這些做法能工作,但會帶來一堆問題 — 從處理器簽名不一致到無法知道事件來自誰或實際發生了什麼。想像一下,如果鍵盤每次按鍵都隨機觸發不同的行為,同一個程式在同一個情境下行為卻不一樣。你剛按下空白鍵預期跳躍,結果卻開了物品欄。那可不是標準!

在 .NET 裡有一套「事件憲法」— 關於處理器簽名和隨事件傳遞資訊的慣例。典型的樣式長這樣:


void Handler(object sender, EventArgs args);

熟悉的字串吧?它到處都出現:從 WinForms 的按鈕點擊到 ASP.NET 的系統事件,甚至第三方函式庫也常用。

2. 什麼是 EventHandlerEventArgs

主要概念:每個事件傳達兩件事

  • 誰觸發了事件? sender
  • 發生了什麼事? EventArgs — 補充資料

在 C# 的世界裡通常這樣表示:


public delegate void EventHandler(object sender, EventArgs e);
  • object sender — 指向事件發起者的參考。可以是任何東西 — 通常是 this
  • EventArgs e — 包含事件額外資訊的物件。對於簡單情境使用 EventArgs.Empty,複雜情境則建立繼承自 EventArgs 的類別。

有趣的小知識

在官方的 .NET 建議中,對公開 API 來說事件應該採用 (object sender, EventArgs e) 的簽名。如果你看到一個沒有 sender 或沒有 EventArgs 的事件,那很可能是作者為了簡化而自創的版本,不是典型的 .NET 風格。

單一標準的大好處

統一的標準讓事件更容易記錄、測試、通用地訂閱與擴充。打開 WinForms、WPF、ASP.NET 的原始碼,你會發現相同的模式到處都是。

3. 如何使用標準事件範本

1. 使用內建的 delegate EventHandler

不用自己聲明 delegate,可以直接用現成的:


public event EventHandler SmthHappened;

這樣處理器看起來總是一樣:


private void OnSmthHappened(object sender, EventArgs e)
{
    // 對事件的回應邏輯
}

訂閱方式依然簡單:


myObj.SmthHappened += OnSmthHappened;

重要:如果事件不傳遞額外資料,請使用 EventArgs.Empty

2. 建立自訂的事件參數

如果你要傳遞資訊(計算結果、檔案名稱、錯誤等),就建立一個繼承自 EventArgs 的類別:


public class CalculationEventArgs : EventArgs
{
    public double Result { get; }
    public CalculationEventArgs(double result) => Result = result;
}

接著使用泛型版的通用 delegate — EventHandler<TEventArgs>


public event EventHandler<CalculationEventArgs> CalculationFinished;

處理器現在接收你自定義的參數類型:


private void OnCalculationFinished(object sender, CalculationEventArgs e)
{
    Console.WriteLine($"計算完成。結果: {e.Result}");
}

4. 範例應用程式

在教學專案上延伸一下 — 假設我們有一個計算機,可以做加減法,並透過事件通知操作完成。

最簡範例


public class Calculator
{
    public event EventHandler<CalculationEventArgs> CalculationPerformed;

    public void Add(int a, int b)
    {
        int result = a + b;
        // 觸發事件
        CalculationPerformed?.Invoke(this, new CalculationEventArgs(result));
    }
}

public class CalculationEventArgs : EventArgs
{
    public int Result { get; }
    public CalculationEventArgs(int result) => Result = result;
}

訂閱並使用:


var calc = new Calculator();
calc.CalculationPerformed += (sender, e) =>
{
    Console.WriteLine($"操作結果: {e.Result}");
};
calc.Add(10, 20);
// 輸出: 操作結果: 30

這就是所謂的「標準」:處理器總是接收發送者物件和事件參數物件 — 通用且容易理解。

5. 有用的細節

封裝事件呼叫:好的風格

在 .NET 中建議把觸發事件的邏輯放在受保護的方法中,方法名稱以 On 為前綴:


protected virtual void OnCalculationPerformed(CalculationEventArgs e)
{
    CalculationPerformed?.Invoke(this, e);
}

在具體邏輯(像是 "Add", "Subtract" 等)裡只要呼叫這個方法:


public void Add(int a, int b) => OnCalculationPerformed(new CalculationEventArgs(a + b));

這種寫法讓繼承類別可以覆寫事件行為,也降低忘記觸發事件的機率。

圖示:.NET 標準事件的結構

graph LR
    A[發布者物件] -- "event EventHandler/ EventHandler<TEventArgs>" --> B[訂閱者清單]
    B -- "處理方法 (object sender, EventArgs e)" --> C[對事件的回應]
    A -- "this (發送者)" --> C
    A -- "EventArgs (資料)" --> C

事件宣告方式比較

做法 傳遞發起者 (sender) 傳遞參數 通用性 .NET 中的使用情況
public event Action<int> MyEvent;
沒有 沒有
public delegate void MyHandler(object, int); event MyHandler ...
不是很常見
public event EventHandler;
沒有 (EventArgs) 是,標準
public event EventHandler<MyArgs>;
有 (MyArgs) 非常高 是,標準

面試跟實戰的實用性

使用標準事件範本是 .NET 開發者的必備技能。在面試中幾乎一定會被問到 EventHandlersender/EventArgs 的相關問題。像 Action<T> 類型的事件通常會被當成「簡化版」看待。

在實際專案中,採用這種方式能讓多人協作、測試、擴充和維護更容易。第三方工具(像是記錄、偵錯工具)也更容易整合,因為它們預期看到的是標準形式。

事件呼叫流程圖

flowchart TD
    subgraph A[發布者類別]
        C1((物件))
        C2[觸發事件的方法]
        C3["event EventHandler<MyArgs>"]
    end
    subgraph B[訂閱者類別]
        D1((訂閱))
        D2["事件處理器 (object sender, MyArgs args)"]
    end
    C1 -- 呼叫 --> C2
    C2 -- "Invoke(this, args)" --> C3
    C3 -- "通知" --> D1
    D1 -- "執行" --> D2

6. 建議、細節與常見錯誤

1. 處理器簽名。 想用 Action<int> 的誘惑很大,但你會失去 sender 與與生態系的相容性。

2. 傳遞參數。 不要把 EventArgs 跟普通參數混在一起。把所有要給處理器的資料放到事件參數物件裡。

3. 對於沒有資料不要用 null。 比起用 null,若無額外資料請使用 EventArgs.Empty

4. 避免弱型別。 不要把所有東西塞到一個萬用的 EventHandler 裡。為每種事件建立專屬的參數類別會讓可讀性和可靠性更高。

5. 呼叫事件時的錯誤。 總是檢查是否有訂閱者:SomeEvent?.Invoke(this, e)。若沒有訂閱者事件變數是 null

6. 避免破壞封裝。 不要在發布者類別外部去觸發事件。事件僅供訂閱/取消訂閱;觸發應該在類別內部透過 On... 類的方法進行。

2
任務
C# SELF, 等級 52, 課堂 3
上鎖
使用 EventHandler 和自訂 EventArgs 創建事件
使用 EventHandler 和自訂 EventArgs 創建事件
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION