C# 2.0 引入**匿名方法**,允許在委派預期的位置直接撰寫方法主體,無需獨立命名方法。這不僅減少樣板代碼,更開啟了**閉包**與**變數捕獲**的強大機制。本文系統剖析語法、編譯器轉換、捕獲語意、效能與現代 Lambda 表達式的關係。
// 宣告委派型別
public delegate void ClickHandler(object sender, EventArgs e);
public class Form1 : Form
{
private Button btn;
public Form1()
{
btn = new Button();
// 實例化委派,指向命名方法
btn.Click += new ClickHandler(OnButtonClick);
}
private void OnButtonClick(object sender, EventArgs e)
{
MessageBox.Show("Clicked!");
}
}
btn.Click += delegate(object sender, EventArgs e)
{
MessageBox.Show("Clicked!");
};
匿名方法可存取**外部區域變數**,編譯器會自動產生**顯示類別**將捕獲變數提升為欄位:
int counter = 0;
btn.Click += delegate
{
counter++; // 捕獲區域變數 counter
Text = $"Clicked {counter} times";
};
編譯器產生等效代碼:
// 編譯器生成的顯示類別
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
public int counter;
public void b__0(object sender, EventArgs e)
{
this.counter++;
this.Text = "Clicked " + this.counter + " times";
}
}
// 實際調用點
var display = new <>c__DisplayClass1();
btn.Click += new EventHandler(display.b__0);
// 錯誤:所有委派共享同一個 i,最終皆印出 10
for (int i = 0; i < 10; i++)
{
buttons[i].Click += delegate { Console.WriteLine(i); };
}
// 正確:引入區域變數副本
for (int i = 0; i < 10; i++)
{
int captured = i; // 每次迴圈新建變數
buttons[i].Click += delegate { Console.WriteLine(captured); };
}
| 限制 | 說明 |
|---|---|
不可使用 ref/out 參數 | 委派簽名若含 ref/out,匿名方法無法匹配 |
不可使用 unsafe 程式碼 | 編譯器不支援在匿名方法中發出 unsafe IL |
不可 goto/break/continue 跳出區塊 | 只能在區塊內部跳轉 |
無法存取 ref/out 外部變數 | 僅能捕獲值型別或參考型別的值 |
C# 3.0 引入 Lambda (`=>`),語法更簡潔、類型推斷更強、支援運算式樹:
// 匿名方法
btn.Click += delegate(object s, EventArgs e) { DoWork(); };
// Lambda 陳述式
btn.Click += (s, e) => { DoWork(); };
// Lambda 運算式 (單行運算式,隱式返回)
Func add = (a, b) => a + b;
// 運算式樹 (供 LINQ to SQL/Entity Framework 解析)
Expression> predicate = u => u.Age > 18;
建議:新代碼**一律使用 Lambda**,匿名方法僅維護舊代碼時接觸。
// 傳統:定義介面、實作類別、注入
public interface IProcessor { void Process(Data d); }
// 匿名方法/Lambda 即時定義策略
public void Execute(Data d, Action strategy)
{
strategy(d);
}
// 呼叫端
Execute(data, d =>
{
d.Validate();
d.Transform();
d.Save();
});
匿名方法是 C# 邁向函數式風格的關鍵一步,引入**內聯委派**與**變數捕獲/閉包**機制。雖已被 Lambda 表達式取代,但理解其編譯器降級轉換(顯示類別)、捕獲語意與限制,對深入掌握 C# 委派、事件、Lambda、LINQ 底層運作至關重要。**核心心法:委派是「可攜帶的執行邏輯」,匿名方法/Lambda 讓邏輯定義與使用無縫融合。**