CodeGym /課程 /C# SELF /使用 delegate 建立事件( event

使用 delegate 建立事件( eventdelegate

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

1. 介紹

所以,當我們在 C# 的語境下說「事件」(event)時,意思是讓一個或多個物件可以被安全地通知某事已發生的機制。事件並不是魔術:底層是 delegate(關鍵字 delegate),並加上防止被誤用的保護。

想像訂閱 YouTube 頻道。頻道(事件擁有者物件)有個「訂閱」按鈕——任何觀眾(其他物件)都可以按它加入訂閱者清單(delegate 處理器)。當作者上傳新影片(觸發事件)時,通知只會發給訂閱者,且只有頻道作者決定何時觸發。重要的一點:觀眾可以訂閱或取消訂閱,但不能自己發送通知——只有頻道擁有者能這樣做。

一點形式化的語法


// 聲明一個 delegate,定義事件處理器的「形狀」
public delegate void SimpleEventHandler();

// 基於 delegate 聲明事件
public event SimpleEventHandler SomethingHappened;

很簡單:event 是用來聲明事件的關鍵字,事件的型別總是由 delegate 指定。

2. 第一個簡單範例:事件「按下按鈕!」

步驟 1. 為事件處理器定義 delegate


// 事件處理器:不回傳、不接受參數
public delegate void ButtonClickHandler();

步驟 2. 含事件的類別


public class Button
{
    // 事件:可以訂閱與取消訂閱,但無法從外部直接觸發
    public event ButtonClickHandler Click;

    // 模擬「按下按鈕」的方法
    public void Press()
    {
        Console.WriteLine("按鈕被按下了!");
        // 觸發事件:通知所有訂閱者
        Click?.Invoke();
    }
}

注意:事件是基於 delegate 聲明的,在方法 Press 裡面使用 ?.Invoke() 來呼叫事件。為什麼?因為事件可能沒有任何訂閱者,此時 Click 會是 null。安全呼叫運算子確保只有在有訂閱者時才會呼叫處理器。

步驟 3. 訂閱事件並執行程式


// 使用範例
public class Program
{
    public static void Main()
    {
        var button = new Button();

        // 加入「監聽者」(訂閱事件)
        button.Click += OnButtonClicked;
        button.Click += () => Console.WriteLine("又一個處理器!");

        button.Press(); // 模擬按鈕被按下

        // 可以取消訂閱事件
        button.Click -= OnButtonClicked;
        button.Press();
    }

    // 一般的事件處理器
    public static void OnButtonClicked()
    {
        Console.WriteLine("處理器:按鈕被按下了!");
    }
}

第一次按下會執行兩個處理器,第二次則只剩下那個 lambda。

3. 事件與 delegate 的差別

對 delegate,可以任意賦值甚至完全替換訂閱者清單——有人可能會寫類似 button.Click = null,這會把之前所有訂閱清掉。

事件則不一樣——它綁定到物件更嚴格。只有宣告該事件的類別的擁有者才能直接觸發事件(例如在類別內部呼叫 Click())。其他程式碼只能透過 +=-= 訂閱或取消訂閱,不能一次把它「清空」。這保護了封裝性,避免訂閱系統被「弄壞」。

4. 簽名:事件 delegate 的型別與參數

在 .NET 的慣例中:事件處理器會接收兩個參數—object sender(是誰觸發事件)與事件參數(EventArgs)。建議使用 delegate EventHandlerEventHandler<TEventArgs>

範例:帶參數的 delegate


public delegate void ButtonClickHandler(object sender, EventArgs e);

EventArgs 是用來傳遞額外資訊的基底類別。需要更多資料時,就建立自己的子類別。

把它應用到我們的按鈕


// 事件參數類別
public class ButtonClickEventArgs : EventArgs
{
    public string UserName { get; }

    public ButtonClickEventArgs(string userName)
    {
        UserName = userName;
    }
}

public class Button
{
    public event EventHandler<ButtonClickEventArgs> Click;

    public void Press(string userName)
    {
        Console.WriteLine("按鈕被按下了!");
        Click?.Invoke(this, new ButtonClickEventArgs(userName));
    }
}

public static void Main()
{
    var button = new Button();
    button.Click += OnButtonClicked;

    button.Press("瓦西里");
}

public static void OnButtonClicked(object sender, ButtonClickEventArgs e)
{
    Console.WriteLine($"使用者 {e.UserName} 按了按鈕!");
}

5. 視覺化示意:事件如何運作


+-------------+
| 使用者      |
+-------------+
      |
      v
+--------------+
|  Button.Press|
+--------------+
      |
      v
+-----------------+         +------------------------+
|  呼叫 Click?    |----->---|   訂閱者 1             |
+-----------------+         +------------------------+
      |                     |   執行處理器           |
      v                     +------------------------+
+-----------------+         +------------------------+
|   如果有訂閱者  |----->---|   訂閱者 2             |
+-----------------+         +------------------------+
      :                     |   執行處理器           |
      v                     +------------------------+

Button.Press 觸發事件;訂閱者的處理器依序執行。

6. 有用的細節

.NET 中推薦的事件形式

使用 EventHandlerEventHandler<TEventArgs>,讓程式碼與函式庫和工具相容。這樣的做法可以讓事件演進更容易:你可以在自己的 EventArgs 加入新屬性而不會破壞訂閱者。

文件: EventHandler, event

事件 vs delegate

如果只需要把一個元件綁到一個明確的方法——delegate 就足夠(delegate)。如果需要程式中不同部分能隨時訂閱或取消訂閱——就用事件(event)。

7. 常見錯誤與尷尬情況

錯誤 #1:在沒有檢查 null 的情況下呼叫事件。 若事件沒有訂閱者,直接呼叫會導致 NullReferenceException。使用安全呼叫:


Click?.Invoke(...);

錯誤 #2:嘗試從類別外部觸發事件。 事件只能在宣告它的類別內被觸發。從其他類別嘗試觸發會造成編譯錯誤——這是為了保護封裝性。

錯誤 #3:對同一個處理器重複訂閱。 如果同一個處理器被多次訂閱,會被呼叫多次。這是訂閱機制的特性。注意不要重複使用 +=,並正確使用 -= 取消訂閱。

2
任務
C# SELF, 等級 52, 課堂 1
上鎖
實作一個簡單的「按下按鈕」事件
實作一個簡單的「按下按鈕」事件
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION