不了解for循环中List <func <int,int =“” >>的行为

时间:2018-07-03 08:31:58

标签: c# loops for-loop lambda func

我刚刚开始学习Lambda表达式,发现了一种对我来说似乎不直观的行为。我怀疑我还不了解基本概念的各个方面。

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

List > 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 + 5等于5)

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

7(...)

8

9

会发生什么情况,就是将Funcs添加到列表中,然后为每个先前添加的Func更新i值。

为什么会这样?

4 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:0)

这是有关捕获/关闭问题的非常书面的内容

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

答案 2 :(得分:0)

这是 capture 的已知作用;在您当前的代码中

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

答案 3 :(得分:0)

捕获局部变量时,它是通过引用捕获的,因此,在函数中使用捕获的变量时,将反映对该变量的任何更新。

编译器将本地变量作为生成的类的字段,基本上发生了与此类似的事情:

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));
    }
}

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

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);
}