CodeGym /課程 /C# SELF /用於 event 和 callback 的 Lambda 表達式

用於 event 和 callback 的 Lambda 表達式

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

1. 介紹

在 C#(以及整個 .NET)中,事件是物件彼此溝通的一種方式。例如,你有一個按鈕,按下它應該觸發某些東西。可以透過宣告一個獨立的方法來處理事件,然後把該方法明確地訂閱到事件……但很多時候你只想「就在這裡描述反應」,不要把邏輯散落在檔案不同地方。

Lambda 表達式非常適合解決這個需求——允許你直接在訂閱事件的地方寫出對事件的反應。最少的手續、最大的便利。

同樣的情況也適用於回呼(callback):有時你會把某段邏輯傳給方法,讓它「之後」執行,例如當某個操作完成時。Lambda 表達式讓這件事簡單且直觀:需要的邏輯就在呼叫位置。

事件與回呼:重點與 Lambda 的使用場景

事件—就是物件上的一個「招牌」:有人可以在這裡訂閱,當某事發生時做點什麼。

Callback(回呼)—就是「我現在給你一個函式,你在需要時呼叫它」。很像「留下申請,我們會回電」的概念。

當你之前學過 delegate 和 event 時,你會見過像這樣的語法:

button.Click += MyButtonClickHandler;

其中 MyButtonClickHandler 是某個需要事先宣告的方法。但當邏輯很簡單時,你會想少寫幾個方法,直接用幾行程式碼解決。

這時候 Lambda 就登場了:

button.Click += (sender, args) => { Console.WriteLine("按鈕被按下!"); };

簡潔、清楚,就寫在這裡!

2. 把 Lambda 當作事件處理器:從簡單到進階

簡單形式:燈泡一閃 — 在控制台輸出點東西

假設有一個類別 Button,我們想對它的 Click 事件做出反應。

// 想像有這樣的按鈕:
public class Button
{
    public event EventHandler? Click;

    public void SimulateClick() // 舉例:程式模擬「按下」
    {
        // 如果有人訂閱了 — 呼叫處理器
        Click?.Invoke(this, EventArgs.Empty);
    }
}

現在我們來使用這個按鈕:

var button = new Button();

// 用 Lambda 訂閱按鈕事件!
button.Click += (sender, args) =>
{
    Console.WriteLine("太棒了!按鈕被按了!");
};

button.SimulateClick(); // => 太棒了!按鈕被按了!

說明:
— 等號右邊的 => 之後的部分就是你的「迷你函式」主體,當事件發生時會被呼叫。
— 不用去管理處理器的生命週期:它的生命由你的訂閱(+=)決定。注意:如果 Lambda 捕獲了外部變數但你沒有取消訂閱,可能會導致記憶體洩漏。

捕獲外部變數的 Lambda 處理器

int clickCount = 0;

button.Click += (sender, args) =>
{
    clickCount++;
    Console.WriteLine($"按鈕已被按了 {clickCount} 次!");
};

button.SimulateClick(); // => 按鈕已被按了 1 次!
button.SimulateClick(); // => 按鈕已被按了 2 次!

發生了什麼:
Lambda 捕獲了外部變數 clickCount,並在多次呼叫間保留其狀態。

3. 將 Lambda 用作 callbacks

回呼 常見於某段程式需要「之後」呼叫你提供的函式,例如在某個耗時工作完成後。

把 Lambda 當作 callback 傳遞

假設有個方法接受 delegate 或 Lambda 作為參數:

void DoWork(Action callback)
{
    Console.WriteLine("開始工作...");
    // 模擬工作
    System.Threading.Thread.Sleep(500); //(實際應用別這樣用,這是阻塞操作!)
    callback(); // 工作結束後呼叫 callback
}

DoWork(() => Console.WriteLine("工作完成!"));

結果:
控制台會先顯示 "開始工作...",然後顯示 "工作完成!"。

你也可以傳遞帶參數的 Lambda:

void Calculate(int a, int b, Action<int> onResult)
{
    int sum = a + b;
    onResult(sum);
}

Calculate(5, 8, result => Console.WriteLine($"結果: {result}"));

4. 使用 Lambda 時的常見錯誤

未取消事件訂閱

當你用 Lambda 訂閱事件時,通常沒有「處理器名稱」可以用來之後取消訂閱:

button.Click += (s, e) => Console.WriteLine("...");
button.Click -= ??? // 如何把你的 Lambda 傳進來?如果沒有先儲存就做不到。

若真的需要取消訂閱 — 把 Lambda 儲存在變數中:

EventHandler handler = (s, e) => Console.WriteLine("...");
button.Click += handler;
// ...然後
button.Click -= handler;

對於長生命週期的物件這點很重要:不取消訂閱會保留參考,可能導致記憶體洩漏。

在迴圈中捕獲變數

經典情況:在迴圈內訂閱事件:

for (int i = 0; i < 3; i++)
    button.Click += (s, e) => Console.WriteLine(i); // 所有 Lambda 「記住」的是同一個 i

在現代的 C# 版本中,這個行為對 foreach 已經修正,但對 for 有時仍需注意——對被捕獲的變數務必要小心!

如果你想讓每個 Lambda 記住自己的值 — 建立一個區域副本:

for (int i = 0; i < 3; i++)
{
    int localI = i;
    button.Click += (s, e) => Console.WriteLine(localI);
}
2
任務
C# SELF, 等級 50, 課堂 3
上鎖
點擊計數器與變數捕獲
點擊計數器與變數捕獲
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION