我刚刚开始学习Lambda表达式,发现了一种对我来说似乎不直观的行为。我怀疑我还不了解基本概念的各个方面。
所以我们有以下两个for循环:
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值。
为什么会这样?
答案 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);
}