依據 C# 2.0 規格第 21 節,本文詳細解析 匿名方法表達式 的語法結構、簽名相容性、隱式轉換規則、區塊限制與 外部變數捕獲 機制。
anonymous-method-expression:
delegate anonymous-method-signature_opt block
anonymous-method-signature:
( anonymous-method-parameter-list_opt )
anonymous-method-parameter-list:
anonymous-method-parameter
anonymous-method-parameter-list , anonymous-method-parameter
anonymous-method-parameter:
parameter-modifier_opt type identifier
primary-no-array-creation-expression,歸類為「無型別值」。| 匿名方法 | 相容委派型別條件 |
|---|---|
無簽名 delegate { } | 委派參數任意,但不可有 out 參數 |
有簽名 delegate(int x) { } | 委派參數數量、型別、ref/out 修飾符完全一致 (params 陣列參數不影響相容性) |
匿名方法 A 隱式轉換為委派型別 D,需滿足:
D 回傳 void → A 內 return 不可帶運算式。D 回傳 R → A 內所有 return expr 的 expr 必須隱式轉換為 R,且 A 區塊端點不可到達 (確保所有路徑都回傳)。
delegate void D(int x);
D d1 = delegate { }; // Ok
D d2 = delegate() { }; // 錯誤:簽名不符
D d3 = delegate(long x) { }; // 錯誤:型別不符
D d4 = delegate(int x) { }; // Ok
D d5 = delegate(int x) { return; }; // Ok
D d6 = delegate(int x) { return x; }; // 錯誤:回傳型別不符 (void)
delegate void E(out int x);
E e1 = delegate { }; // 錯誤:委派有 out 參數
E e2 = delegate(out int x) { x = 1; }; // Ok
E e3 = delegate(ref int x) { x = 1; }; // 錯誤:ref/out 不符
delegate int P(params int[] a);
P p1 = delegate { }; // 錯誤:區塊端點可到達
P p2 = delegate { return; }; // 錯誤:回傳型別不符
P p3 = delegate { return 1; }; // Ok
P p4 = delegate { return "Hi"; }; // 錯誤:型別不符
P p5 = delegate(int[] a) { return a[0]; }; // Ok
P p6 = delegate(params int[] a) { return a[0]; }; // 錯誤:不可有 params 修飾符
new D(delegate { Console.WriteLine("hi"); })
// 等價於
(D)delegate { Console.WriteLine("hi"); }
ref/out 參數。this 不可存取 (防止值型別複製語意混淆)。goto/break/continue 跳出區塊或進入巢狀匿名方法。return 從最近封閉匿名方法返回,非封閉函數成員。包含匿名方法的作用域內的區域變數、值參數、參數陣列,以及實例方法中的 this,皆為外部變數。
delegate int D();
static D F()
{
int x = 0;
return delegate { return ++x; }; // 捕獲 x
}
static void Main()
{
D d = F();
Console.WriteLine(d()); // 1
Console.WriteLine(d()); // 2
Console.WriteLine(d()); // 3
}
被捕獲變數生命週期延長至委派可被 GC 回收為止。每次呼叫委派操作同一變數實例。
// 情況 A:變數在迴圈內宣告 → 每次迭代新實例
for (int i = 0; i < 3; i++)
{
int x = i * 2 + 1;
result[i] = delegate { Console.WriteLine(x); };
}
// 輸出: 1, 3, 5
// 情況 B:變數在迴圈外宣告 → 共用單一實例
int x;
for (int i = 0; i < 3; i++)
{
x = i * 2 + 1;
result[i] = delegate { Console.WriteLine(x); };
}
// 輸出: 5, 5, 5
int x = 0;
for (int i = 0; i < 3; i++)
{
int y = 0;
result[i] = delegate { Console.WriteLine($"{++x} {++y}"); };
}
// 輸出: 1 1 / 2 1 / 3 1 (x 共用,y 各自獨立)
int x = 0;
Setter s = delegate(int v) { x = v; };
Getter g = delegate { return x; };
s(5); Console.WriteLine(g()); // 5
s(10); Console.WriteLine(g()); // 10
匿名方法透過嚴格的簽名/回傳型別相容規則實現隱式轉換,並以閉包機制捕獲外部變數、延長其生命週期。下篇將續述 匿名方法評估、方法群組轉換 及 編譯器優化 規範。