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);
}
GO TO FULL VERSION