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、默认参数 等等,这些统称为“语法糖”。

 
分类: .NET
原文地址:https://www.cnblogs.com/Leo_wl/p/3422311.html