【问题标题】:why this C# program outputs such a result? How do I understand closure?为什么这个 C# 程序会输出这样的结果?我如何理解闭包?
【发布时间】:2011-07-22 18:29:16
【问题描述】:

我试图理解这个问题的答案Why am I getting wrong results when calling Func<int>? 我写了一些示例代码。以下代码

public static void Main(string[] args)
{
    var funcs = new List<Func<string>>();
    for(int v=0,i=0;v<3;v++,i++)
    {
        funcs.Add( new Func<string>(delegate(){return "Hello "+ i++ +" "+v;}) );
    }
    foreach(var f in funcs)
        Console.WriteLine(f());
}

产生

Hello 3 3
Hello 4 3
Hello 5 3

在阅读了 Jon Skeet 和 Eric Lippert 的解释后,我想我会明白的

Hello 3 3
Hello 3 3
Hello 3 3

这里 v 和 i 都是循环变量,而 i 的值是在那一刻被拾取的 v 不是为什么会这样?。我不明白这种行为。

【问题讨论】:

  • 您意识到您将i 增加了两次?
  • 是的,增量是有意的。我只是想检查一下。这就是这个问题的根本原因。

标签: c# .net closures


【解决方案1】:

答案很简单:++i 在您的委托内部执行,因此每次都会增加值。第一个值为 3,因为这是循环后 i 的值。
了解您的委托不是在 for 循环内执行,而是在 foreach 循环内执行。

【讨论】:

    【解决方案2】:

    嗯,您正确理解了 Eric 和 Jon,但是您错过了代码的一部分:

    "Hello "+ i++ +" "+v;
              ^^^
              this part increments i for each call
    

    所以基本上,发生的事情是这样的:

    1. 捕获匿名函数3次,捕获对方法中变量的引用,不在循环范围内
    2. 在循环结束时,这两个变量的值都为 3
    3. 执行第一个函数,输出iv的内容,然后递增i
    4. 执行第二个函数,输出iv的内容,因为这和之前的方法调用是一样的i,所以这里输出的是4,而不是3
    5. 等等

    另一方面,如果您通过在循环范围内捕获变量来更改代码,如下所示:

    for(int v=0,i=0;v<3;v++,i++)
    {
        int ii = i, vv = v;
        funcs.Add( new Func<string>(delegate(){return "Hello "+ ii++ +" "+vv;}) );
    }
    

    然后你会得到0, 01, 12, 2。您仍在增加 ii 变量,在循环中使用捕获的值后执行此操作,但随后您再也不会使用该变量(每个匿名方法都有自己的私有副本。)感谢@ferosekhanj评论

    【讨论】:

    • 感谢 lasse 的出色回答。现在我理解了这种行为。对您的 ii,vv 代码进行一次更正。我们都会得到 0,1,2。这就是循环变量捕获问题的解决方案。无论如何,您的回答仍然澄清了很多事情。
    【解决方案3】:

    结果是正确的(怎么可能不正确?;)) 当你执行委托时,循环结束后,它会使用 i 和 v 变量的当前值。

    v 不会再改变,循环结束时 v == 3。我 == 3 太。但是您的代表将 i 写入输出,然后将其递增 (i++)。因此,每次执行委托时,都会增加 i,但不会增加 v。

    这就是你所观察到的。

    【讨论】:

      【解决方案4】:

      我认为 for 循环变量具有 for 循环外部的范围。

      public static void Main(string[] args)
      {
          var funcs = new List<Func<string>>();
          int i=0;
          for(int v=0;v<3;v++,i++)
          {
              funcs.Add( new Func<string>(delegate(){return "Hello "+ i++ +" "+v;}) );
          }
          foreach(var f in funcs)
              Console.WriteLine(f());
      }
      

      在 for 循环 i==3v==3 之后。因为代码在创建委托之间没有留下i 的范围,所以委托的所有三个实例共享相同的i。因此,对函数的每次调用都会增加相同的i,然后您会得到3,4,5

      【讨论】:

        猜你喜欢
        • 2011-07-10
        • 1970-01-01
        • 2013-10-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多