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

CLR 中匿名函數實現原理深度解析(二)

     編輯:關於C語言

在 C# 2.0 中,編譯器會根據匿名函數的使用場景自動決定生成的方法是否為 static。這也是為何規範禁止在匿名方法中使用 goto、break、continue 跳出,或從外部跳入的原因——雖然代碼寫在同一作用域,但實際編譯後並不在同一位置。

更方便的是,編譯器能根據上下文自動推斷匿名函數的參數,無需開發者顯式指定。例如:

button1.Click += delegate(Object sender, EventArgs e) { MessageBox.Show("The Button was clicked!"); };

若不使用參數,可簡寫為:

button1.Click += delegate { MessageBox.Show("The Button was clicked!"); };

相較於匿名函數本身的實現,更複雜的是它對外層作用域變量的捕獲與生命週期管理。微軟的 Grant Ri 在部落格中有系列文章深入探討此主題:

  • Anonymous Methods, Part 1 of ?
  • Anonymous Methods, Part 2 of ?
  • Anonymous Method Part 2 answers

核心挑戰有二:一是匿名函數如何訪問不在同一作用域的父函數或類變量;二是被捕獲變量的生命週期必須與匿名函數綁定,而非父函數的調用週期。這兩點迫使 C# 編譯器採用獨立類封裝的方式來實現。

首先,匿名函數使用的父函數局部變量(無論引用類型或值類型)都必須從棧上轉移到堆上,以便在作用域外仍可被訪問並控制生命週期。因為棧變量的生命週期與擁有者函數綁定,函數返回後棧框銷毀,無法實現變量生命週期與函數調用的解耦。

例如下面這個簡單案例,匿名函數捕獲了父函數的局部變量 i,雖然僅在父函數內部使用,編譯器仍會生成封裝類:

delegate void Delegate1();
public void Method1()
{
    int i = 0;
    Delegate1 d1 = delegate() { i++; };
    d1();
}

編譯器生成的封裝代碼類似如下:

delegate void Delegate1();
private sealed class __LocalsDisplayClass$00000002
{
    public int i;
    public void __AnonymousMethod$00000001()
    {
        this.i++;
    }
}
public void Method1()
{
    __LocalsDisplayClass$00000002 local1 = new __LocalsDisplayClass$00000002();
    local1.i = 0;
    Delegate1 d1 = new Delegate1(local1.__AnonymousMethod$00000001);
    d1();
}

這種「顯式環境類」模式正是 C# 實現閉包的核心機制,將捕獲變量提升為堆上物件的欄位,確保其生命週期延長至委托不再被引用為止。

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