首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 2.0 匿名方法規範深度解析(一):語法、簽名、轉換與外部變量捕獲

     編輯:關於C#

C# 2.0 引入匿名方法,允許在委托預期的位置直接內聯代碼塊。本文依據語言規範(第 21 章),系統梳理匿名方法的語法結構、簽名規則、隱式轉換條件、委托創建表達式、方法體限制、外部變量捕獲機制、實例化語義及運行時計算行為。

21.1 匿名方法表達式

語法形式:

delegate anonymous-method-signature_opt block
  • anonymous-method-signature 為可選的參數列表 (parameter-list_opt)。
  • 參數格式:parameter-modifier_opt type identifier(可含 ref/out)。
  • 匿名方法表達式被歸類為「無類型值」,僅能透過隱式轉換為兼容委托類型。
  • 它為參數、局部變量、常數及標籤建立新的聲明空間。

21.2 匿名方法簽名

  • 若有簽名,兼容委托需參數類型、修飾符、順序完全一致;委托最後一個參數是否為 params 不影響兼容性。
  • 若無簽名,兼容委托可有任意數量參數,但不得包含 out 參數。
  • 匿名方法參數名不得與外部作用域中的局部變量、常數或參數同名(編譯錯誤)。

21.3 匿名方法隱式轉換

匿名方法表達式 A 與委托類型 D 兼容的條件:

  1. 參數兼容性:
    • A 無簽名 → D 可有零或多參數,但無 out 參數。
    • A 有簽名 → D 必須參數數量、類型、ref/out 修飾符逐一對應。
  2. 返回類型兼容性(忽略巢狀匿名方法塊):
    • D 為 void → A 中 return 不得帶表達式。
    • D 為類型 R → A 中所有 return 表達式需隱式轉換至 R,且塊結束點不可達。

除到兼容委托類型的隱式轉換外,匿名方法無其他轉換(甚至不可轉為 object)。

轉換規則範例

delegate void D(int x);
D d1 = delegate { };        // OK:無簽名匹配無 out 參數委托
D d2 = delegate() { };      // 錯誤:有空簽名但委托有參數
D d4 = delegate(int x) { }; // 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

delegate int P(params int[] a);
P p3 = delegate { return 1; };    // OK:塊結束點不可達
P p5 = delegate(int[] a){ return a[0]; }; // OK:params 不影響兼容性

21.3.1 委托創建表達式

new D(delegate { ... }) 等價於 (D)delegate { ... },同樣遵循上述隱式轉換規則。

21.4 匿名方法塊限制

  • 無簽名時,參數不可在塊內訪問;有簽名時,參數作用域為塊內。
  • 禁止訪問最近封閉簽名以外的 ref/out 參數。
  • 結構類型中禁止訪問 this(顯式或隱式)。
  • 可訪問外部變量(§21.5),計算時引用活躍實例。
  • 禁止 goto/break/continue 跳出塊或進入巢狀匿名方法。
  • return 從最近封閉匿名方法返回,而非外層函數成員。
  • 編譯器實現時合成的命名方法/類型名稱保留雙下劃線前綴(如 __Method1)。

21.5 外部變量與捕獲機制

21.5.1 什麼是外部變量?

作用域包含匿名方法表達式的局部變量、值參數、參數陣列,以及實例函數成員中的 this(視為值參數)。

21.5.2 捕獲與生命週期延長

當外部變量在匿名方法中被引用,即被「捕獲」。捕獲變量的生命週期至少延長至引用該匿名方法的委托可被垃圾回收為止。

範例:捕獲局部變量 x

delegate int D();
class Test {
    static D F() {
        int x = 0;
        D result = delegate { return ++x; };
        return result;
    }
    static void Main() {
        D d = F();
        Console.WriteLine(d()); // 1
        Console.WriteLine(d()); // 2
        Console.WriteLine(d()); // 3
    }
}

三次調用操作同一 x 實例,輸出 1、2、3。

21.5.3 局部變量實例化與捕獲語義

局部變量在進入作用域時實例化。若在循環中聲明,每次迭代產生新實例;若移至循環外,則共用單一實例。

循環內聲明 → 每次迭代獨立實例

static D[] F() {
    D[] result = new D[3];
    for (int i = 0; i < 3; i++) {
        int x = i * 2 + 1;
        result[i] = delegate { Console.WriteLine(x); };
    }
    return result;
}
// 輸出:1, 3, 5

循環外聲明 → 共用單一實例

static D[] F() {
    D[] result = new D[3];
    int x;
    for (int i = 0; i < 3; i++) {
        x = i * 2 + 1;
        result[i] = delegate { Console.WriteLine(x); };
    }
    return result;
}
// 輸出:5, 5, 5

混合捕獲:共享 x、獨立 y

static D[] F() {
    D[] result = new D[3];
    int x = 0;
    for (int i = 0; i < 3; i++) {
        int y = 0;
        result[i] = delegate { Console.WriteLine("{0} {1}", ++x, ++y); };
    }
    return result;
}
// 輸出:
// 1 1
// 2 1
// 3 1

多匿名方法共享同一變量實例

int x = 0;
Setter s = delegate(int value) { x = value; };
Getter g = delegate { return x; };
s(5); Console.WriteLine(g()); // 5
s(10); Console.WriteLine(g()); // 10

21.6 匿名方法計算(運行時行為)

  • 計算產生委托實例,引用匿名方法及計算時活躍的被捕獲外部變量集合。
  • 委托調用列表僅含單一入口;目標對象與方法未指定(可能為 null、this 或其他)。
  • 語義相同且捕獲相同變量集合的匿名方法,允許 返回相同委托實例(優化機會)。

優化範例

delegate double Function(double x);
class Test {
    static double[] Apply(double[] a, Function f) { ... }
    static void F(double[] a, double[] b) {
        a = Apply(a, delegate(double x) { return Math.Sin(x); });
        b = Apply(b, delegate(double y) { return Math.Sin(y); });
        // 兩匿名方法語義相同、捕獲集合均為空 → 編譯器可使其共用委托實例
    }
}

總結

C# 2.0 匿名方法規範完整定義了語法、類型轉換、變量捕獲與生命週期、運行時語義。掌握這些細節,能幫助開發者正確理解閉包行為、避免常見陷阱(如循環中變量捕獲),並為後續 Lambda 表達式與 LINQ 的學習奠定堅實基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved