而這裡自動生成的函數是否為static,編譯器根據使用此函數的地方是否static決定。這也是為什麼C#2.0規范裡面禁止使用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!"); };
相對於匿名函數的實現來說,比較復雜的是匿名函數對於其父作用域中變量的使用及其實現。MS的GrantRi在其blog上有一系列的討論文章。
Anonymous Methods, Part 1 of ?
Anonymous Methods, Part 2 of ?
Anonymous Method Part 2 answers
需要解決的問題有兩個:一是不在一個變量作用域中的匿名函數如何訪問父函數和類的變量;二是匿名函數使用到的變量的生命周期必須與其綁定,而不能與父函數的調用生命周期綁定。這兩個問題使得C#編譯器選擇較為復雜的獨立類封裝方式實現匿名函數和相關變量生命周期的管理。
首先,匿名函數使用到的父函數中局部變量,無聊是引用類型還是值類型,都必須從棧變量轉換為堆變量,以便在其作用域外的匿名函數實現代碼可以訪問並控制生命周期。因為棧變量的生命周期與其所有者函數是一致的,所有者函數退出後,其堆棧自動恢復到調用函數前,也就無法完成變量生命周期與函數調用生命周期的解耦。
例如下面這個簡單的匿名函數中,使用了父函數的局部變量,雖然此匿名函數只在父函數裡面使用,但C#編譯器還是使用獨立類對其使用到的變量進行了包裝。
以下為引用:
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();
}