循环中的Lambda变量捕获 - 这里发生了什么?

Eve*_*ers 5 .net c# lambda

我想试试看,这里发生了什么?编译器生成什么样的代码?

public static void vc()
{
    var listActions = new List<Action>();

    foreach (int i in Enumerable.Range(1, 10))
    {
        listActions.Add(() => Console.WriteLine(i));
    }

    foreach (Action action in listActions)
    {
        action();
    }
}

static void Main(string[] args)
{ 
  vc();
}
Run Code Online (Sandbox Code Playgroud)

输出: 10 10 .. 10

根据这个,ActionHelper的新实例会为每次迭代创建.那么在这种情况下,我认为它应该打印1..10.有人能给我一些编译器在这里做的伪代码吗?

谢谢.

Tig*_*ran 10

在这一行

 listActions.Add(() => Console.WriteLine(i));
Run Code Online (Sandbox Code Playgroud)

i捕获变量,或者如果您愿意,创建一个指向该变量的内存位置的指针.这意味着每个委托都有一个指向该内存位置的指针.执行此循环后:

foreach (int i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}
Run Code Online (Sandbox Code Playgroud)

由于明显的原因i10,使得所有的指针存在于存储器内容Action(多个)被指向,变为10.

换句话说,i被捕获.

顺便说一句,应该注意,根据埃里克利珀,这种"奇怪"的行为将被 解决C# 5.0.

因此在C# 5.0您的程序中将按预期打印:

1,2,3,4,5...10
Run Code Online (Sandbox Code Playgroud)

编辑:

找不到Eric Lippert关于主题的帖子,但这是另一个:

重新审视循环中的闭包


Joe*_*orn 9

编译器生成什么样的代码?

这听起来像你期望的那样:

foreach (int temp in Enumerable.Range(1, 10))
{
    int i = temp;
    listActions.Add(() => Console.WriteLine(i));
}
Run Code Online (Sandbox Code Playgroud)

对每次迭代使用不同的变量,因此将捕获创建lambda时变量的值.实际上,您可以使用这个确切的代码并获得您想要的结果.

但是编译器实际上做的更接近于此:

int i;
foreach (i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}
Run Code Online (Sandbox Code Playgroud)

这清楚地表明您在每次迭代时捕获相同的变量.当您稍后去实际执行该代码时,它们都会引用已经增加到10 的相同值.

不是编译器或运行时中的错误......这是语言设计团队的一个有意识的决定.然而,由于对其工作原理的困惑,他们已经改变了这一决定,并决定冒险进行突破性改变,使其更像您对C#5的期望.