• 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
  • 相关阅读:
    [解题报告]HDU 1094 A+B for InputOutput Practice (VI)
    [解题报告]HDU 1089 A+B for InputOutput Practice (I)
    [解题报告]HDU 1279 验证角谷猜想
    [解题报告]HDU 1091 A+B for InputOutput Practice (III)
    [解题报告]HDU 2019 数列有序!
    [解题报告]HDU 1201 18岁生日
    [解题报告]HDU 1170 Balloon Comes!
    11使用TensorFlow自定义模型和训练
    12使用TensorFlow加载和预处理数据
    10训练深度神经网络
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3422311.html
Copyright © 2020-2023  润新知