奇怪的Lambda行为

11

我偶然发现了这篇文章,觉得非常有趣,于是我进行了一些测试:

测试一:

List<Action> actions = new List<Action>();

for (int i = 0; i < 5; ++i)
    actions.Add(() => Console.WriteLine(i));

foreach (Action action in actions)
    action();

输出:

5
5
5
5
5

第二个测试:

List<Action> actions = new List<Action>();

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

foreach (Action action in actions)
    action();

输出:

0
1
2
3
4
根据这篇文章,在Test One中,所有的lambda表达式都包含对 i 的引用,这导致它们都输出5。那么这是否意味着在Test Two中我会得到预期的结果,因为每个lambda表达式都创建了一个新的 int ?

1
是的...因为你在每次迭代中将i赋值给j,所以lambda捕获了对j的新引用-第二个测试的结果符合预期。我手头没有Jon Skeet的C#深度,所以无法参考他关于在lambda中捕获变量的信息。我记得他确实详细介绍了在lambda中捕获值的一些细节。 - IAbstract
如果您具有C/C++背景,可以将其视为传递内存地址而不是值本身。因此,您正在传递&i - 在循环结束时,i == 5。 j在作用域内创建,因此其值在之后不会被修改。 - Rob
这个问题已经被问了很多次... - leppie
这个问题在JavaScript上下文中每天都会被一些程序员问几次,他们还没有得到有关闭包和变量捕获的适当介绍。 - zzzzBov
3个回答

10

这是因为C#中的变量捕获可能有些棘手。

简而言之,for循环的每次迭代都引用同一个变量i,因此编译器对所有循环使用相同的lambda表达式。

如果你需要安慰的话,这种怪异行为在JavaScript中更糟糕,因为JavaScript只有函数作用域,所以即使使用第二种解决方案也无法达到预期效果。

这也是一个非常好的解释


我不会说这是一个“问题”,而是一个特性。尽管当你第一次遇到它时,它可能不太直观。 - Rob

7

1

是的。

在测试一中,变量i在循环中被捕获,但i指的是在循环外部有效声明的变量,因此所有捕获的Lambda引用同一个变量。在调用操作时,i的值为5,因此所有输出都为5。

在测试二中,变量j在循环中被捕获,但在这种情况下,j在每次循环内部声明,因此所有捕获的Lambda引用不同的变量。因此,调用lambda函数会输出不同的值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接