【问题标题】:Don't understand behaviour of List<Func<int, int>> in for-loop不理解 for 循环中 List<Func<int, int>> 的行为
【发布时间】:2018-07-03 08:31:57
【问题描述】:

我刚刚开始接触 Lambda-Expressions,并发现了一种对我来说似乎不直观的行为。我怀疑我还没有理解基本概念的各个方面。

所以我们有这两个 for 循环:

列表> list = new List>();

  for (int i = 0; i < 5; i++)
  {
    list.Add(j => j + i);
  }
  for (int i = 0; i < 5; i++)
  {
    Console.WriteLine(list[i](i));
  }

我原本期望的输出是这样的:

0(因为 j+0 且 j=0 等于 0)

2(因为 j+1 且 j=1 等于 2)

4 (…)

6

8

而是输出显示:

5(我怀疑 j+5 和 j=0 等于 5)

6(我怀疑 j+5 和 j=1 等于 6)

7 (…)

8

9

如果将 Func 添加到 List 中,则会为每个先前添加的 Func 更新 i 值。

为什么会这样?

【问题讨论】:

    标签: c# loops for-loop lambda func


    【解决方案1】:

    那是因为局部变量i 只捕获最后一个值。您应该创建一个具有局部范围的单独变量(超出下一次迭代范围的变量):

    for (int i = 0; i < 5; i++)
    {
        int l = i;
        list.Add(j => j + l);
    }
    

    【讨论】:

    • 我明白,但为什么会这样?我从来不知道递增的局部变量 i 影响了列表的其他 Func 元素。为什么它不是静态的 - 毕竟它不是 func 元素的参数!?
    • 因为变量的作用域。
    • @TheSlazzer 是的,它是一个参数。
    • 我现在看到了,它是一个参数,因为捕获。谢谢!
    【解决方案2】:

    这是一篇关于 Capture/Closure 问题的文章

    for (int i = 0; i < 5; i++)
    {
       var newI = i;
       list.Add(j => j + newI);
    }
    for (int i = 0; i < 5; i++)
    {
       Console.WriteLine(list[i](i));
    }
    

    输出

    0
    2
    4
    6
    8
    

    【讨论】:

      【解决方案3】:

      当一个局部变量被捕获时,它是通过引用来捕获的,所以当你在函数中使用捕获的变量时,对变量的任何更新都会反映出来。

      编译器将局部变量提升为生成类的字段,基本上发生的事情是这样的:

      class Closure
      {
          public int i;
          public int Fn(int j) => i + j;
      }
      static void Main(string[] args)
      {
          List<Func<int, int>> list = new List<Func<int, int>>();
          var c = new Closure();
          for (c.i = 0; c.i < 5; c.i++)
          {
              list.Add(c.Fn);
          }
          for (int i = 0; i < 5; i++)
          {
              Console.WriteLine(list[i](i));
          }
      }
      

      您可以通过在循环中声明一个局部变量来绕过此行为 o 编译器将捕获该局部变量,并且由于其语义是变量在每次循环迭代中必须不同,因此编译器将生成一个单独的为每次迭代捕获实例。

      for (int i = 0; i < 5; i++)
      {
          int l = i;
          list.Add(j => j + l);
      }
      // Equivalent to :
      
      for (var i = 0; i < 5; i++)
      {
          var c = new Closure();
          c.i = i;
          list.Add(c.Fn);
      }
      

      【讨论】:

        【解决方案4】:

        这是一个已知的捕获效果;在您当前的代码中

        for (int i = 0; i < 5; i++)
        {
            // each lambda uses shared "i" variable
            list.Add(j => j + i);
        }
        
        // Now (after the for loop) i == 5, 
        // that's why all lambdas j => j + i are in fact j => j + 5
        

        如果你想避免i变量捕获,你可以把你的代码改成

        for (int i = 0; i < 5; i++)
        {
            // local variable: each iteration has its own temp to be captured
            int temp = i;
        
            list.Add(j => j + temp);
        }
        
        //  1st lambda j => j + temp equals to j => j + 0
        //  2nd lambda j => j + temp equals to j => j + 1
        // ...
        // n-th lambda j => j + temp equals to j => j + n - 1
        

        【讨论】:

        • 我知道发生了什么事 (j+5) 但想知道这是怎么回事。我现在知道这是因为捕获,谢谢!
        猜你喜欢
        • 1970-01-01
        • 2013-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多