Hulk' Den

in-depth thinking and keep moving.

从闭包说起

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

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

一、邂逅[闭包]

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

function a() { 
    var i = 0; 
    function b() { 
    alert(++i); 
    } 
    return b; 
} 
var c = a(); 
c(); 

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

二、直面[闭包]

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

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

public class Program
{
    public static Action funcB()
    {
        Console.WriteLine("funcB Begin..");
        int i = 0;
        i++;
        Console.WriteLine("funcB:" + i);
        Action action = () =>
        {
            Console.WriteLine("funcA:" + i);
        };
        i = 100;
        Console.WriteLine("funcB:" + i);
        Console.WriteLine("funcB End..");
        return action;
    }
    static void Main()
    {
        var action = funcB();
        action();
        Console.ReadKey();
    }
}
public class Program
{
    public static async Task funcA(Action callback)
    {
        //停留5秒,缓下节奏
        await Task.Delay(5000);
        Console.WriteLine("funcA continue..");
        callback();
    }
    public static void funcB()
    {
        Console.WriteLine("funcB Begin..");
        int i = 0;
        i++;
        Console.WriteLine("funcB:" + i);
        funcA(() =>
        {
            Console.WriteLine("funcA:" + i);
        });
        i = 100;
        Console.WriteLine("funcB:" + i);
    }
    static void Main()
    {
        funcB();
        Console.WriteLine("funcB End..");
        Console.ReadKey();
    }
}

 

  两个写法目的一样,就是想勾起大家的所有疑问。代码段[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]:

public class Program
{
    //定义一个全局的c__DisplayClass1类型的变量。
    static c__DisplayClass1 displayCls;
    /// <summary>
    /// 这就是类似于编译器的那个自定义类<>c__DisplayClass1
    /// </summary>
    sealed class c__DisplayClass1
    {
        public int i;

        public void b_0()
        {
            Console.WriteLine("funcA:" + i);
        }
    }
    public static Action funcB()
    {
        displayCls = new c__DisplayClass1();
        Console.WriteLine("funcB Begin..");
        displayCls.i = 0;
        displayCls.i++;
        Console.WriteLine("funcB:" + displayCls.i);
        Action action = displayCls.b_0;
        displayCls.i = 100;
        Console.WriteLine("funcB:" + displayCls.i);
        Console.WriteLine("funcB End..");
        return action;
    }
    static void Main()
    {
        var action = funcB();
        action();
        Console.ReadKey();
    }
}

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

五、终极想象

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

public class Program
{
    public static List<Action> funcB()
    {
        List<Action> list = new List<Action>();
        Console.WriteLine("funcB Begin..");
        int i = 0;
        i++;
        Console.WriteLine("funcB:" + i);
        Action action1 = () =>
        {
            Console.WriteLine("funcA:" + i);
            i = 200;
        };
        Action action2 = () =>
        {
            Console.WriteLine("funcA:" + i);
        };
        i = 100;
        Console.WriteLine("funcB:" + i);
        Console.WriteLine("funcB End..");
        list.Add(action1);
        list.Add(action2);
        return list;
    }
    static void Main()
    {
        var action = funcB();
        action[0]();
        action[1]();
        Console.ReadKey();
    }
}

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

 

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

  

最近的文章

将自定义的值类型用作字典的键,要特别注意什么?

  前天关注了老赵的微信公众号:赵人希。昨天就推送了一个快速问答,下面把我的答题经历跟大家分享,希望对于菜鸟同胞们有所帮助启发。  其实这个问题在他的博客里有专门的篇幅讲解,我猜到了,哈哈。但是大牛讲问题都是在一个高度上,水平差点的需要费点力气才能理解,而这个过程就是拉进我们与大牛距离的过程,用心总结下,才能不断强化自己靠近他们。一、通常的字典用法(熟练可直接略过)  在我平时的编码中,用的最频繁的就是代码段[1]:public class Example{ public stati...…

C# | .NET继续阅读
更早的文章

对[yield]的浅究到发现[async][await]

  上篇对[foreach]的浅究到发现[yield]写完后,觉得对[yield]还没有理解清楚,想起曾经看过一位大牛的帖子讲的很深刻(链接在此),回顾了下,在这里写出自己的理解,与各位分享。一、通常的异步  现在我们假设一种平时经常遇到的情况,现有三个方法,其中funcOne和funcTwo比较耗时需要异步执行,而且他们的逻辑是必须在funcOne执行完后才可以执行funcTwo,同理funcTwo执行完后才能执行funcThree。  按照这样的设定,通常的做法请看代码段[1]:pub...…

C# | .NET继续阅读