• [点滴系列][1]:从闭包说起


       世界杯车轮战开始了,连通三天,基本进入世界杯状态。看球也不能忘了玩技术,这次打算把接触c#以来的点滴总结起来,让原本模糊的概念清晰起来,博友们一起来吧!

      [闭包]接触这个词的第一感觉就是晦涩难懂,下面我们就来啃一啃。

    一、邂逅[闭包]

      第一次接触闭包是在js里,先来看代码段[1]:

    1 function a() { 
    2     var i = 0; 
    3     function b() { 
    4         alert(++i); 
    5     } 
    6     return b; 
    7 } 
    8 var c = a(); 
    9 c(); 
    js的闭包

      很简单的代码,细心观察会发现变量i的作用域是在方法a中,也就是说出了方法a后变量i就不起作用了,可代码中的i依然活跃在方法c中,是不是违背了程序的基本规则呢?球出界了队员还在踢。

    二、直面[闭包]

      先来啃啃[闭包]的正经概念,[闭包(Closure)]就是引用了自由变量的表达式,通常是函数(也就是代码段[1]中的函数b)。它的特点是被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外,通俗的讲就是大家常说的闭包延长了变量的生命期。对照代码段[1],清晰些了吗?

      下面来看c#版的,代码段[2]:

     1     public class Program
     2     {
     3         public static Action funcB()
     4         {
     5             Console.WriteLine("funcB Begin..");
     6             int i = 0;
     7             i++;
     8             Console.WriteLine("funcB:" + i);
     9             Action action = () =>
    10             {
    11                 Console.WriteLine("funcA:" + i);
    12             };
    13             i = 100;
    14             Console.WriteLine("funcB:" + i);
    15             Console.WriteLine("funcB End..");
    16             return action;
    17         }
    18         static void Main()
    19         {
    20             var action = funcB();
    21             action();
    22             Console.ReadKey();
    23         }
    24     }
    c#的闭包 写法1
     1     public class Program
     2     {
     3         public static async Task funcA(Action callback)
     4         {
     5             //停留5秒,缓下节奏
     6             await Task.Delay(5000);
     7             Console.WriteLine("funcA continue..");
     8             callback();
     9         }
    10         public static void funcB()
    11         {
    12             Console.WriteLine("funcB Begin..");
    13             int i = 0;
    14             i++;
    15             Console.WriteLine("funcB:" + i);
    16             funcA(() =>
    17             {
    18                 Console.WriteLine("funcA:" + i);
    19             });
    20             i = 100;
    21             Console.WriteLine("funcB:" + i);
    22         }
    23         static void Main()
    24         {
    25             funcB();
    26             Console.WriteLine("funcB End..");
    27             Console.ReadKey();
    28         }
    29     }
    c#的闭包 写法2

      两个写法目的一样,就是想勾起大家的所有疑问。代码段[2]的运行结果是什么呢?为什么是这样的结果呢?[闭包]真的延长了变量的生命期吗?相信熟悉的人是清楚的,我们要做的是继续深挖。

    三、深挖[闭包]

      我们都懂这只是语法糖,它并没有违背程序的基本规律。下面就抄家伙(.NET Reflector)来窥窥究竟。

                      

                      图[1]                                                                                     图[2]

     

                                                      图[3]

                                                       图[4]

     

                                                        图[5]

    一下上了5张图,不要慌,慢慢来。

    图[1]是Reflector中Program类的字段、方法、类等的名称列表。我们注意到,除去我们代码中定义的,编译器还自动生成了一个类:c__DisplayClass1 !!

    图[2]是编译器自动生成的类c__DisplayClass1中的一个方法<funcB>b__0的定义,其实就是funcB方法中的那个匿名函数 ()=>{Console.WriteLine("funcA:" + i);} 的实现;

    图[3]是编译器自动生成的类c__DisplayClass1的实现的IL代码,请注意方法<funcB>b__0和公共变量i

    图[4]、图[5]是funB方法的IL代码,每一段代表的啥意思我都大概做了标注。可以看到:在方法的一开始,编译器就初始化了c__DisplayClass1类的一个实例,之后对于变量i的操作,在IL中其实就是对于起初初始化的那个全局的c__DisplayClass1类实例中的变量i的操作,所以说[闭包]延长了变量的生命期是假象,其实我们一直在操作一个全局的类实例的变量。

    四、模仿[闭包]

      原理基本清楚了,下面我们来自己动手模仿一下编译器做的事。

      代码段[3]:

     1     public class Program
     2     {
     3         //定义一个全局的c__DisplayClass1类型的变量。
     4         static c__DisplayClass1 displayCls;
     5         /// <summary>
     6         /// 这就是类似于编译器的那个自定义类<>c__DisplayClass1
     7         /// </summary>
     8         sealed class c__DisplayClass1
     9         {
    10             public int i;
    11 
    12             public void b_0()
    13             {
    14                 Console.WriteLine("funcA:" + i);
    15             }
    16         }
    17         public static Action funcB()
    18         {
    19             displayCls = new c__DisplayClass1();
    20             Console.WriteLine("funcB Begin..");
    21             displayCls.i = 0;
    22             displayCls.i++;
    23             Console.WriteLine("funcB:" + displayCls.i);
    24             Action action = displayCls.b_0;
    25             displayCls.i = 100;
    26             Console.WriteLine("funcB:" + displayCls.i);
    27             Console.WriteLine("funcB End..");
    28             return action;
    29         }
    30         static void Main()
    31         {
    32             var action = funcB();
    33             action();
    34             Console.ReadKey();
    35         }
    36     }
    类似闭包

      编译器费尽心思给我们做了一个语法糖,让我们的编程更加轻松优雅。

    五、终极想象

      只上代码,代码段[4]:

     1     public class Program
     2     {
     3         public static List<Action> funcB()
     4         {
     5             List<Action> list = new List<Action>();
     6             Console.WriteLine("funcB Begin..");
     7             int i = 0;
     8             i++;
     9             Console.WriteLine("funcB:" + i);
    10             Action action1 = () =>
    11             {
    12                 Console.WriteLine("funcA:" + i);
    13                 i = 200;
    14             };
    15             Action action2 = () =>
    16             {
    17                Console.WriteLine("funcA:" + i);
    18             };
    19             i = 100;
    20             Console.WriteLine("funcB:" + i);
    21             Console.WriteLine("funcB End..");
    22             list.Add(action1);
    23             list.Add(action2);
    24             return list;
    25         }
    26         static void Main()
    27         {
    28             var action = funcB();
    29             action[0]();
    30             action[1]();
    31             Console.ReadKey();
    32         }
    33     }
    终极测试

      运行结果是什么呢?自己动手,丰衣足食

      就到这儿吧,有问题的地方希望各位指正,敬礼!

      

  • 相关阅读:
    压测 swoole_websocket_server 性能
    Laravel本地环境搭建:Homestead开发环境的部署
    laravel5.5框架中视图间如何共享数据?视图间共享数据的两种方法
    laravel框架模型model的创建与使用方法
    laravel学习:主从读写分离配置的实现
    《梦断代码》阅读笔记一
    数组结对开发
    对搜狗输入法的评价
    几个一
    对于大家的评审提出改进方案
  • 原文地址:https://www.cnblogs.com/such/p/2600976.html
Copyright © 2020-2023  润新知