在循环λ变量捕获 - 这里是什么情况?(Lambda variable capture in loo

2019-06-18 12:39发布

我试图让我的头左右,是什么在这里发生了什么? 该编译器产生什么样的代码?

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

输出:10 10 .. 10

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

谢谢。

Answer 1:

在这条线

 listActions.Add(() => Console.WriteLine(i));

可变i ,被捕获,或者如果希望,创建了一个指针 ,该变量的存储器位置。 这意味着, 每一个代表了一个指向内存位置。 这个循环执行后:

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

出于显而易见的原因i10 ,使得存在于所有的指针的存储器内容Action (多个)被指向,变为10。

换句话说, i被捕获。

顺便说一句,应该注意,根据埃里克利珀,这个“奇怪”的现象将得到解决C# 5.0

因此,在C# 5.0程序将打印预期

1,2,3,4,5...10

编辑:

找不到埃里克利珀对主题的帖子,但这里是另一个问题:

封闭在循环再探



Answer 2:

该编译器产生什么样的代码?

这听起来像你想到这个:

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

使用不同 varaible对于每次迭代,所以在拉姆达被创建时的变量的值将被捕获。 事实上,你可以使用这个确切的代码,并得到你想要的结果。

但是,编译器实际上做的是更接近于这样的:

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

这清楚地表明,你正在拍摄在每次迭代相同的变量。 当你以后去实际执行的代码,它们都是指已被递增达10 相同的值。

不是在编译或运行时错误......这是在语言设计团队的部分有意识的决定。 然而,由于对如何工作的混乱,他们已经逆转这一决定,并决定冒险重大更改,使期待C#5工作更喜欢你 。



Answer 3:

在本质上正在发生的事情是,编译器声明你int i之外循环的,因此不能为每个操作创建它的一个新的价值,只是引用每次相同。 到时候你执行的代码我是10,所以它打印10了很多。



文章来源: Lambda variable capture in loop - what happens here?
标签: c# .net lambda