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

C# 2.0 匿名方法深度解析:委派內聯、捕獲變數與最佳實踐

     編輯:關於C語言

前言

C# 2.0 引入**匿名方法**,允許在委派預期的位置直接撰寫方法主體,無需獨立命名方法。這不僅減少樣板代碼,更開啟了**閉包**與**變數捕獲**的強大機制。本文系統剖析語法、編譯器轉換、捕獲語意、效能與現代 Lambda 表達式的關係。

從委派到匿名方法

傳統委派寫法 (C# 1.x)

// 宣告委派型別
public delegate void ClickHandler(object sender, EventArgs e);

public class Form1 : Form
{
    private Button btn;

    public Form1()
    {
        btn = new Button();
        // 實例化委派,指向命名方法
        btn.Click += new ClickHandler(OnButtonClick);
    }

    private void OnButtonClick(object sender, EventArgs e)
    {
        MessageBox.Show("Clicked!");
    }
}

匿名方法寫法 (C# 2.0)

btn.Click += delegate(object sender, EventArgs e)
{
    MessageBox.Show("Clicked!");
};
  • 關鍵字 `delegate` 取代委派建構函式
  • 參數列表可**省略**(若不使用參數):`delegate { ... }`
  • 主體為語句區塊,可包含多行邏輯

變數捕獲與閉包

匿名方法可存取**外部區域變數**,編譯器會自動產生**顯示類別**將捕獲變數提升為欄位:

int counter = 0;
btn.Click += delegate 
{
    counter++; // 捕獲區域變數 counter
    Text = $"Clicked {counter} times";
};

編譯器產生等效代碼:

// 編譯器生成的顯示類別
[CompilerGenerated]
private sealed class <>c__DisplayClass1
{
    public int counter;
    public void b__0(object sender, EventArgs e)
    {
        this.counter++;
        this.Text = "Clicked " + this.counter + " times";
    }
}

// 實際調用點
var display = new <>c__DisplayClass1();
btn.Click += new EventHandler(display.b__0);

捕獲陷阱:迴圈變數

// 錯誤:所有委派共享同一個 i,最終皆印出 10
for (int i = 0; i < 10; i++)
{
    buttons[i].Click += delegate { Console.WriteLine(i); };
}

// 正確:引入區域變數副本
for (int i = 0; i < 10; i++)
{
    int captured = i; // 每次迴圈新建變數
    buttons[i].Click += delegate { Console.WriteLine(captured); };
}

匿名方法限制

限制說明
不可使用 ref/out 參數委派簽名若含 ref/out,匿名方法無法匹配
不可使用 unsafe 程式碼編譯器不支援在匿名方法中發出 unsafe IL
不可 goto/break/continue 跳出區塊只能在區塊內部跳轉
無法存取 ref/out 外部變數僅能捕獲值型別或參考型別的值

Lambda 表達式演進

C# 3.0 引入 Lambda (`=>`),語法更簡潔、類型推斷更強、支援運算式樹:

// 匿名方法
btn.Click += delegate(object s, EventArgs e) { DoWork(); };

// Lambda 陳述式
btn.Click += (s, e) => { DoWork(); };

// Lambda 運算式 (單行運算式,隱式返回)
Func add = (a, b) => a + b;

// 運算式樹 (供 LINQ to SQL/Entity Framework 解析)
Expression> predicate = u => u.Age > 18;

建議:新代碼**一律使用 Lambda**,匿名方法僅維護舊代碼時接觸。

效能與記憶體考量

  • 顯示類別分配:每次捕獲變數的匿名方法建立,都會 `new` 顯示類別實例。高頻熱路徑(如遊戲迴圈、高頻事件)應避免捕獲,或改用靜態方法/結構體。
  • 委派實例快取:相同靜態方法/無捕獲 Lambda 會被編譯器快取為單一委派實例(`static readonly` 欄位),零分配。
  • 實例方法委派:綁定 `this` 會配置委派物件(含目標物件參考),較靜態方法稍貴。

實務模式:策略模式簡化

// 傳統:定義介面、實作類別、注入
public interface IProcessor { void Process(Data d); }

// 匿名方法/Lambda 即時定義策略
public void Execute(Data d, Action strategy)
{
    strategy(d);
}

// 呼叫端
Execute(data, d => 
{
    d.Validate();
    d.Transform();
    d.Save();
});

除錯技巧

  • Visual Studio 偵錯時,匿名方法顯示為 `b__0` 等編譯器生成名稱。
  • 呼叫堆疊中可見 `DisplayClass` 物件,檢查其欄位即可觀察捕獲變數值。
  • ReSharper/Rider 可「轉換為 Lambda」或「提取為方法」。

總結

匿名方法是 C# 邁向函數式風格的關鍵一步,引入**內聯委派**與**變數捕獲/閉包**機制。雖已被 Lambda 表達式取代,但理解其編譯器降級轉換(顯示類別)、捕獲語意與限制,對深入掌握 C# 委派、事件、Lambda、LINQ 底層運作至關重要。**核心心法:委派是「可攜帶的執行邏輯」,匿名方法/Lambda 讓邏輯定義與使用無縫融合。**

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