匿名方法是 C# 2.0 引入的語法糖,允許以內嵌程式碼區塊直接建立委派實例,省去獨立方法定義。本文深入剖析其編譯器實作機制、靜態/實例/區域變數捕獲行為、閉包特性與迴圈變數捕獲陷阱,助開發人員正確高效運用此特性。
傳統委派需定義獨立方法:
public delegate bool SelectItem(string sItem);
string[] AStrings = objMyCol.GetFilteredItemArray(FilterStringWithA); // 需額外定義 FilterStringWithA 方法
匿名方法以 delegate 關鍵字內嵌邏輯:
string[] AStrings = objMyCol.GetFilteredItemArray(delegate(string sItem)
{
return sItem[0] == 'A';
});
優點:程式碼內聚、避免類別膨脹。缺點:過大區塊降低可讀性,易造成重複。
.NET CLR 僅能執行屬於某類型的靜態或實例方法。C# 編譯器在編譯期會自動產生對應方法:
若匿名方法未使用任何外部變數,編譯器在定義該匿名方法的類別中產生 private static 方法封裝邏輯,效能最佳。
private static 方法this.xxx)→ 產生 private instance 方法,需存取 this此為最複雜也最強大的機制。編譯器:
範例轉換:
// 原始碼
int iTemp = 100;
MyDelegate dlg = delegate { Console.WriteLine(iTemp); };
dlg();
// 編譯器產生的等效邏輯
private class __Closure
{
public int iTemp;
public void __Method() { Console.WriteLine(iTemp); }
}
// Main 中
__Closure loc = new __Closure();
loc.iTemp = 100;
MyDelegate dlg = new MyDelegate(loc.__Method);
dlg();
區域變數生命週期因此延伸至委派存活期間,這正是閉包特性。
若匿名方法定義於巢狀區塊並捕獲多層區域變數,編譯器為每層作用域產生對應巢狀類別,內層類別持有外層類別實例參考:
int iTemp = 100;
if (iTemp > 50)
{
int jTemp = 200;
dlg = delegate { Console.WriteLine(iTemp + ", " + jTemp); };
}
// 產生兩個巢狀類別:__ClosureScope1 (含 iTemp)、__ClosureScope2 (含 jTemp + __ClosureScope1 參考 + 匿名方法)
問題:在 for 迴圈中捕獲迴圈變數 i:
for (int i = 1; i <= 5; i++)
{
MyDelegate tempD = delegate { Console.WriteLine(i); };
d += tempD;
}
d(); // 輸出 6, 6, 6, 6, 6 (非預期 1,2,3,4,5)
原因:編譯器將 i 提升至巢狀類別單一實例欄位,所有委派共享同一實例。迴圈結束時 i=6,故所有委派輸出 6。
解法:在迴圈內建立區域副本:
for (int i = 1; i <= 5; i++)
{
int k = i; // 每次迭代建立新區域變數
MyDelegate tempD = delegate { Console.WriteLine(k); };
d += tempD;
}
d(); // 正確輸出 1,2,3,4,5
此時編譯器為每次迭代產生獨立巢狀類別實例,各委派捕獲獨立的 k 值。
匿名方法不僅簡化委派語法,更透過閉包機制實現區域變數捕獲,極大豐富了委派的表達能力。理解其編譯器實作細節(靜態/實例方法、巢狀類別閉包)對避免迴圈變數陷阱、正確管理變數生命週期至關重要。善用此特性可編寫更簡潔、更具表達力的 C# 程式碼。