C# 2.0 引入匿名方法,允許以內聯程式碼區塊建立委派實例,省去額外方法定義,減少類別雜訊。語法:delegate(參數) { 主體; }。
// 傳統寫法:需額外定義 FilterStringWithA、FilterStringWithT 方法
string[] aStrings = col.GetFilteredItemArray(FilterStringWithA);
// 匿名方法:內聯定義過濾條件
string[] aStrings = col.GetFilteredItemArray(delegate(string s) {
return s[0] == 'A';
});
CLR 無法直接執行「自由流動」的程式碼區塊,每個方法必須屬於某個型別,且為靜態或實例方法。C# 編譯器會自動在定義匿名方法的類別中產生私有靜態/實例方法,並以此方法位址取代匿名方法。
// 原始碼
delegate { Console.WriteLine("Hello"); };
// 編譯器產生
private static void __AnonymousMethod$001() { Console.WriteLine("Hello"); }
ILDasm 可見新增的 private static 方法。
靜態方法即可存取靜態資料,效率較高。
public class Program {
int count = 0;
void Test() {
Action d = delegate { Console.WriteLine(++count); };
d();
}
}
// 編譯器產生
private void __AnonymousMethod$002() { Console.WriteLine(++this.count); }
必須為實例方法才能透過 this 存取實例欄位。
當匿名方法引用定義它的方法中的區域變數時,編譯器會產生一個私有巢狀類別,將該區域變數「提升」為該類別的公用欄位,並將匿名方法包裝為該類別的實例方法。
// 原始碼
int temp = 100;
Action d = delegate { Console.WriteLine(temp); };
d();
// 編譯器轉換後等效程式碼
class __ClosureClass {
public int temp;
public void __Method() { Console.WriteLine(temp); }
}
__ClosureClass loc = new __ClosureClass();
loc.temp = 100;
Action d = new Action(loc.__Method);
d();
區域變數 temp 的生命週期延長至委派存活期間,這就是閉包。
外層作用域變數 → 產生第一個巢狀類別;內層作用域變數 + 匿名方法 → 產生第二個巢狀類別,並持有對第一個類別實例的參考。
Action d = null;
for (int i = 1; i <= 5; i++) {
Action tempD = delegate { Console.WriteLine(i); };
d += tempD;
}
d(); // 預期 1 2 3 4 5,實際輸出 6 6 6 6 6
原因:i 被提升至單一閉包類別實例的欄位,迴圈結束時 i=6,所有委派共享同一實例,故皆印出 6。
for (int i = 1; i <= 5; i++) {
int k = i; // 每次迭代新建區域變數
Action tempD = delegate { Console.WriteLine(k); };
d += tempD;
}
// 輸出 1 2 3 4 5
每次迭代產生獨立閉包實例,捕獲當下的 k 值。
() => ...)本質相同,語法更簡潔。