C# 闭包

2019-08-13  本文已影响0人  周末的游戏之旅

闭包的概念

内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。但该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。

闭包的优点

使用闭包,我们可以轻松的访问外层函数定义的变量,例如在匿名方法中。比如有如下场景,在WinForm中,我们希望当用户关闭窗体时,给用户一个提示。代码如下:

private void Form1_Load(object sender, EventArgs e)
{
       string tipWords = "您将关闭当前对话框";
       this.FormClosing += delegate
       {
            MessageBox.Show(tipWords);
       };
}

若不使用匿名方法,我们就需要使用其他方式将tipWords变量的值传递给FormClosing注册的处理函数,这就增加了不必要的工作量。

闭包陷阱

例如有如下代码:

private static void Before()
{
    Action[] actions = new Action[10];

    for (var i = 0; i < actions.Length; i++)
    {
        actions[i] = () =>
        {
            Console.WriteLine(i);
        };
    }

    foreach (var item in actions)
    {
        item();
    }
}

输出结果


闭包陷阱解释:

  1. 在for循环中,只能有一个 i 变量。即在第一次循环时,i 的地址就分配好了(注意了,这里i的地址第一次分配后是不变的),不会因为循环次数的多少而发生任何改变,其改变的只能是里面装载的值。
  2. 当使用匿名方法时传进去的是变量的地址,而不是具体值。只有当真正执行这个匿名方法时,才会去确定它的值。这就是为什么上面的例子中,其结果均为10(for循环在最后,当i=9时,i又加了1)。

编译器帮我们做了什么

下面的代码是编译器帮我们生成的代码

private static void After()
{
    Action[] actions = new Action[10];

    var anonymous = new AnonymousClass();

    for (anonymous.i = 0; anonymous.i < actions.Length; anonymous.i++)
    {
        actions[anonymous.i ] = anonymous.Action;
    }

    foreach (var item in actions)
    {
        item();
    }
}

class AnonymousClass
{
    public int i;

    public void Action()
    {
        Console.WriteLine(this.i);
    }
}

如何避免闭包陷阱

  1. 就是在for循环中,定义一临时变量tmpNum,存储i的值即可。因为编译器会对该临时变量重新分配内存,这样,每次循环,都重新分配新的内存,就不会有这个问题了。
  2. 使用foreach,c#5.0后,for和foreach在处理闭包问题上有了一些新的改变。为了适应不同的需求,微软对佛 foreach做了调整,“foreach”的遍历中定义的临时循环变量会被逻辑上限制在循环内,“foreach”的每次循环都会是循环变量的一个拷贝,这样闭包就看起来关闭了(没有了)。
上一篇 下一篇

猜你喜欢

热点阅读