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

C# 2.0 泛型規範深度解析(三):匿名方法完整規範與捕獲變量語義

     編輯:關於C語言

第 21 章:匿名方法——內聯委托的語言級支援

C# 2.0 引入匿名方法,允許在需要委托值的地方直接編寫代碼塊,類似 Lambda 表達式的前身。它不僅簡化語法,更通過外部變量捕獲實現閉包語義,極大增強了委托的表達力。本文完整解析規範第 21 章:表達式語法、簽名兼容、轉換規則、塊限制、外部變量捕獲與生命週期、計算語義、委托創建優化。

21.1 匿名方法表達式語法

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)*
anonymous_method_parameter:
  parameter_modifier_opt  type  identifier
  • 歸類為無類型值,僅透過隱式轉換至兼容委托類型。
  • 定義新宣告空間(參數、區域變數、常數、標籤)。

21.2 匿名方法簽名與委托兼容性

有簽名 vs 無簽名

  • 有簽名:兼容委托需參數數量、類型、修飾符(ref/out)完全一致。最後參數是否 params 不影響兼容性。
  • 無簽名:兼容無 out 參數的任意委托(參數數量、類型不限)。參數在塊內不可訪問。

返回類型兼容

  • 委托返回 void → 匿名方法中 return 不得帶運算式。
  • 委托返回 R → 所有 return 運算式需隱式轉換至 R,且塊結束點不可達(即所有路徑必須 return/throw)。

示例

delegate void D(int x);
D d1 = delegate { };           // OK (無簽名)
D d2 = delegate() { };         // 錯誤:簽名不匹配 (無參 vs 1參)
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; }; // 錯誤:返回類型不匹配

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 "Hello"; }; // 錯誤
P p5 = delegate(int[] a) { return a[0]; } // OK
P p6 = delegate(params int[] a) { return a[0]; } // 錯誤:匿名方法簽名不可含 params

21.3 匿名方法轉換

  • 僅存在隱式轉換至兼容委托類型。
  • 無轉換至 object 或其他類型。
  • 委托創建表達式 new D(delegate { ... }) 等價於強制轉換 (D)delegate { ... }。

21.4 匿名方法塊限制

限制說明
參數訪問有簽名:參數可訪問。無簽名:參數不可訪問(即使委托有參數)。
ref/out 參數僅能訪問最近封閉匿名方法簽名中的 ref/out 參數。外層方法的 ref/out 禁止訪問。
this 訪問若 this 為結構類型 → 編譯錯誤(顯式 this.x 或隱式 x 皆禁)。類類型無限制。
外部變量可自由訪問捕獲的外部變量(21.5)。計算時引用活躍實例。
跳轉語句塊內 goto/break/continue 目標若在塊外或內嵌匿名方法塊內 → 錯誤。
return從最近封閉匿名方法返回,非封閉函數成員。運算式需與委托兼容。
實現細節編譯器可合成命名方法/類型實現,合成名稱保留雙底線(__)。

21.5 外部變量與捕獲機制

定義

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

21.5.1 捕獲與生命週期延長

外部變量被匿名方法引用即被捕獲。正常區域變量生命週期限於關聯塊/語句執行期;捕獲變量生命週期至少延長至引用其的委托符合垃圾回收條件為止。

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

x 被捕獲,生命週期超出 F(),三次調用操作同一實例,輸出 1,2,3。

捕獲變量不再視為 fixed(固定),變為 movable(可移動)。Unsafe 代碼取址需先用 fixed 語句固定。

21.5.2 區域變量實例化與捕獲語義

程序執行進入變量作用域時發生實例化。循環中聲明 → 每次迭代實例化一次;循環外聲明 → 單次實例化。這直接影響捕獲行為:

// 情況 A:循環內聲明 → 每次迭代新實例
static D[] F() {
  D[] result = new D[3];
  for (int i = 0; i < 3; i++) {
    int x = i * 2 + 1;          // 每次迭代新 x
    result[i] = delegate { Console.WriteLine(x); };
  }
  return result;
}
// 輸出:1, 3, 5

// 情況 B:循環外聲明 → 單一實例
static D[] F() {
  D[] result = new D[3];
  int x;
  for (int i = 0; i < 3; i++) {
    x = i * 2 + 1;              // 同一 x
    result[i] = delegate { Console.WriteLine(x); };
  }
  return result;
}
// 輸出:5, 5, 5

編譯器允許但不強制將語義相同、捕獲變量集合相同的匿名方法優化為同一委托實例。

共享與隔離捕獲

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 匿名方法計算

  • 運行時計算產生委托實例,捕獲變量集合在計算時刻為活躍。
  • 委托調用列表單一條目。目標對象、目標方法、this 值均未指定(實現細節)。
  • 語義相同、捕獲集合相同的匿名方法可(但不必)返回同一委托實例(允許優化)。
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); });
  // 兩匿名方法語義相同、捕獲集合均為空 → 編譯器可合並為同一委托
}

21.7 委托相等性(補充)

兩個委托實例相等(==)條件:同類型、同目標對象、同目標方法。匿名方法產生的委托目標未指定,故不保證相等性比較結果,除非編譯器合並優化。

總結:匿名方法的設計精髓

  1. 語法糖與語義核心:表面是委托字面量,本質是閉包實現(捕獲外部變量)。
  2. 生命週期管理:捕獲變量提升至堆上(編譯器生成顯式類別封裝),由委托引用計數決定銷毀。
  3. 實例化語義:區域變量聲明位置決定捕獲粒度(每次迭代獨享 vs 全共享),這是最易踩坑之處。
  4. 限制的合理性:禁止 ref/out 跨層訪問、結構 this 訪問、跳轉語句逃逸,皆為防止破壞棧幀/閉包結構完整性。
  5. 演進:C# 3.0 Lambda 表達式(=>)在語法上簡化匿名方法,語義完全繼承。理解匿名方法規範即理解 Lambda 底層機制。

掌握第 21 章規範,不僅能精確預測捕獲行為、避免閉包陷阱,更能深入理解 C# 委托、事件、LINQ、異步編程的基石機制。

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