.NET:C# 如何實現的閉包?


背景

C# 在編譯器層面為我們提供了閉包機制(Java7 和 Go 也是這種思路),本文簡單的做個解釋。

背景知識

你必須了解:引用類型、值類型、引用、對象、值類型的值(簡稱值)。

關於引用、對象和值在內存的分配有如下幾點規則:

  • 對象分配在堆中。
  • 作為字段的引用分配在堆中(內嵌在對象中)。
  • 作為局部變量(參數也是局部變量)的引用分配在棧中。
  • 作為字段的值分配在堆中(內嵌在對象中)。
  • 作為局部變量(參數也是局部變量)的值用分配在棧中。
  • 局部變量只能存活於所在的作用域(方法中的大括號確定了作用域的長短)。

注:按值傳遞和按引用傳遞也是需要掌握的知識點,C# 默認是按值傳遞的。

閉包示例

測試代碼

 1         private static void Before()
 2         {
 3             Action[] actions = new Action[10];
 4 
 5             for (var i = 0; i < actions.Length; i++)
 6             {
 7                 actions[i] = () =>
 8                 {
 9                     Console.WriteLine(i);
10                 };
11             }
12 
13             foreach (var item in actions)
14             {
15                 item();
16             }
17         }

輸出結果

編譯器幫我們做了是什么?

編譯器幫我們生成的代碼(我自己寫的,可以使用 Reflector 工具自己查看)

 1         private static void After()
 2         {
 3             Action[] actions = new Action[10];
 4 
 5             var anonymous = new AnonymousClass();
 6 
 7             for (anonymous.i = 0; anonymous.i < actions.Length; anonymous.i++)
 8             {
 9                 actions[anonymous.i ] = anonymous.Action;
10             }
11 
12             foreach (var item in actions)
13             {
14                 item();
15             }
16         }
17 
18         class AnonymousClass
19         {
20             public int i;
21 
22             public void Action()
23             {
24                 Console.WriteLine(this.i);
25             }
26         }

如何修復上面的問題?

上面的例子不是我們期望的輸出,讓我們給出兩種修改方案:

第一種(借鑒JS)

 1         private static void Fix()
 2         {
 3             Action[] actions = new Action[10];
 4 
 5             for (var i = 0; i < actions.Length; i++)
 6             {
 7                 new Action<int>((j) =>
 8                 {
 9                     actions[i] = () =>
10                     {
11                         Console.WriteLine(j);
12                     };
13                 })(i);
14             }
15 
16             foreach (var item in actions)
17             {
18                 item();
19             }
20         }

第二種

 1         public static void Fix2()
 2         {
 3             Action[] actions = new Action[10];
 4 
 5             for (var i = 0; i < actions.Length; i++)
 6             {
 7                 var j = i;
 8                 
 9                 actions[i] = () =>
10                 {
11                     Console.WriteLine(j);
12                 };
13             }
14 
15             foreach (var item in actions)
16             {
17                 item();
18             }
19         }

分析

編譯器將閉包引用的局部變量轉換為匿名類型的字段,導致了局部變量分配在堆中。

備注

C# 編譯器幫我們做了非常多的工作,如:自動屬性、類型推斷、匿名類型、匿名委托、Lamda 表達式、析構方法、await 和 sync、using、對象初始化表達式、lock、默認參數 等等,這些統稱為“語法糖”。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM