匿名方法應用和機制
先看一段代碼(C#1.0):
1 //這裡加了參數<為了說明delegate關鍵字後面的參數列表> 2 public delegate void MyDelegate(object sender, EventArgs e); 3 class Program 4 { 5 static void Main() 6 { 7 new Program().function(); 8 } 9 private void function()10 {11 MyDelegate md1 = new MyDelegate(MyMethod);12 md1(this, EventArgs.Empty);13 }14 private void MyMethod(object sender, EventArgs e)15 {16 Console.WriteLine("C#1.0寫法");17 }18 }
到了C#2.0時就可以省略掉起到為多委託聲明的書寫,直接內聯方式寫MyMethod方法:
private void function(){ MyDelegate md2 = delegate { Console.WriteLine("C#2.0寫法"); }; md2(this,EventArgs.Empty);}
難道真的省去了中間的方法了嗎?看看IL代碼:
發現多了一個《'<function>b__0'》的靜態方法,如下是此方法代碼:
//編譯器還是產生了一個方法,這個方法名很怪<以使用匿名方法的所在方法名開頭>//注意delegate { Console.WriteLine("C#2.0寫法"); };的delegate 後面我們沒有寫//參數<所以匿名方法內部將用不到參數了>,編譯器產生了些命名奇怪的參數.method private hidebysig static void '<function>b__0'(object A_0, class [mscorlib]System.EventArgs A_1) cil managed{ //下面這兩行表示這些代碼是編譯器產生的,而不是我們手動寫的 .custom instance void[mscorlib]System.Runtime.CompilerServices .CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // 代碼大小 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr bytearray (43 00 23 00 32 00 2E 00 30 00 99 51 D5 6C ) // C.#.2...0..Q.l IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret} // end of method Program::'<function>b__0'
在delegate關鍵字後面可以不指定參數[結果就是你無法使用參數,但是參數還是實實在在的存在的,只是它的命名是編譯器決定的,很奇怪的名字,你拿不到], 但是如果指定參數,則必須全部指定,拿上面的例子說,我只用到了[object sender和EventArgs e中的一個參數],只指定一個行不行,答案是不行的,你要麼不指定,要麼全部指定, 從委託的簽名來說這點很好理解
匿名方法允許我們以內聯的方式簡化代碼書寫[本質還是相同的,並沒有效能的提升,只是把我們以前做的工作由編譯器代做了]。 注意到編譯器產生的這個方法是個靜態方法,看過有些文章說用在執行個體方法內部的匿名方法會產生一個執行個體方法,說的不完整, 我做過幾個測試,結果是在前提條件是在執行個體方法中,當你用到執行個體成員時,編譯器才會產生執行個體方法,當你沒有用到時, 編譯器產生一個靜態方法。當然在靜態方法中使用匿名方法則產生一個"匿名"的靜態方法,而不會是執行個體方法了。
匿名方法的中的"外部變數"
這個外部變數指的是匿名方法所在的方法裡的局部變數,對與匿名方法來說稱作外部變數, 改寫下上面的代碼[只改了function這個方法,其他沒動]:
private void function(){ int i = 10; MyDelegate md2 = delegate { i++; Console.WriteLine("C#2.0寫法"); }; md2(this, EventArgs.Empty);}
雖然聲明的這個i沒做什麼實質性的工作,但是它卻使編譯器做了很多額外的工作,看圖:
我們的《'<function>b__0'》跑到一個內部類裡去了,編譯器調用到外部變數產生了一個內部類[私人密封的類]! 而把外部變數作為了這個類的一個共有欄位,我還原下改寫的那個function方法[註:把用友好點的代碼寫一下]:
1 private void function() 2 { 3 Temp tempClass = new Temp(); 4 tempClass.i = 10; 5 MyDelegate md2 = new MyDelegate(tempClass.TempMethod); 6 md2(this, EventArgs.Empty); 7 } 8 private sealed class Temp 9 {10 public int i;11 12 public void TempMethod(object sender, EventArgs e)13 {14 i++;15 Console.WriteLine("C#2.0寫法");16 }17 }
這個開銷還是蠻大的[另外i的值也變的不確定了,慎用外部變數!],用new一個類實現外部變數的傳遞。 外部變數的生命週期被延長了,一直持續到引用該匿名方法的委託符合記憶體回收的條件為止。對i的引用是在建立該委託時捕獲的。 匿名方法不能訪問外部範圍的ref和out參數。
總結
總結:C#2.0中的匿名方法僅僅是編譯器簡化我們建立委託執行個體的過程,是一種文法糖,與C#1.0中的寫法沒有本質的區別。另外匿名方法不是方法...