CLR 中匿名函數的實現原理淺析
C# 2.0中提供了通過delegate實現匿名函數功能,能有效地減少用戶的薄記代碼工作,例如
以下為引用:
...
button1.Click += new EventHandler(button1_Click);
...
void button1_Click(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...
可以被簡化為直接使用匿名函數構造,如
以下為引用:
...
button1.Click += delegate(Object sender, EventArgs e) {
// Do something, the button was clicked...
}
...
關於匿名函數的使用方法可以參考Jeffrey Richter的Working with Delegates Made EasIEr with C# 2.0一文。簡要說來就是C#編譯器自動將匿名函數代碼轉移到一個自動命名函數中,將原來需要用戶手工完成的工作自動完成。例如構造一個私有靜態函數,如
以下為引用:
class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(delegate(Object obj) { Console.WriteLine(obj); }, 5);
}
}
被編譯器自動轉換為
以下為引用:
class AClass {
static void CallbackWithoutNewingADelegateObject() {
ThreadPool.QueueUserWorkItem(new WaitCallback(__AnonymousMethod$00000002), 5);
}
private static void __AnonymousMethod$00000002(Object obj) {
Console.WriteLine(obj);
}
}
而這裡自動生成的函數是否為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的Grant Ri在其blog上有一系列的討論文章。
Anonymous Methods, Part 1 of ?
Anonymous Methods, Part 2 of ?
Anonymous Method Part 2 answers
需要解決的問題有兩個:一是不在一個變量作用域中的匿名函數如何訪問父函數和類的變量;二是匿名函數使用到的變量的生命周期必須與其綁定,而不能與父函數的調用生命周期綁定。這兩個問題使得C#編譯器選擇較為復雜的獨立類封裝方式實現匿名函數和相關變量生命周期的管理。
首先,匿名函數使用到的父函數中局部變量,無聊是引用類型還是值類型,都必須從棧變量轉換為堆變量,以便在其作用域外的匿名函數實現代碼可以訪問並控制生命周期。