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

C# 循环中的Lambda变量捕获-这里发生了什么?,c#,.net,lambda,C#,.net,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 li

我在想办法,这里发生了什么?编译器生成什么类型的代码

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();
}
publicstaticvoidvc()
{
var listActions=new List,每次迭代都会创建一个ActionHelper的新实例。
有人能给我一些编译器在这里做什么的伪代码吗

谢谢。

在这行

 listActions.Add(() => Console.WriteLine(i));
捕获变量
i
,或者如果您愿意,创建指向该变量内存位置的指针。这意味着每个委托都获得指向该内存位置的指针。此循环执行后:

foreach (int i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}
由于明显的原因,
i
10
,因此
操作
(s)中所有指针指向的内存内容变为10

换句话说,
i
被捕获

顺便说一句,应该注意的是,根据Eric Lippert的说法,这种“奇怪”的行为将在
C#5.0
中得到解决

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

编辑:

找不到Eric Lippert关于该主题的帖子,但这里有另一篇:


本质上,编译器在循环之外声明您的
int i
,因此不会为每个操作创建新的值,每次只引用相同的值。执行代码时,i为10,因此它会大量打印10

编译器生成什么类型的代码

这听起来像是你所期待的:

foreach (int temp in Enumerable.Range(1, 10))
{
    int i = temp;
    listActions.Add(() => Console.WriteLine(i));
}
这会在每次迭代中使用不同的变量,因此将捕获创建lambda时变量的值。事实上,您可以使用这段精确的代码并获得所需的结果

但编译器实际上做的是与此更接近的事情:

int i;
foreach (i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}
这表明您在每次迭代中都捕获了相同的变量。当您稍后实际执行该代码时,它们都引用了相同的值,该值已经递增到10


这不是编译器或运行时中的错误…这是语言设计团队的一个有意识的决定。然而,由于对其工作原理的混淆,他们已经推翻了这个决定。

似乎这不再是VS2013和.NET 4.5I在我的回答中链接的问题,但由于这个问题很可能会出现d排在投票名单的首位:C#5.0于2012年8月发布,并包含在Visual Studio 2012中,因此技术上它已经“修复”@PaoloMoretti:或者最好等待service pack 1?:)我认为对于新程序员来说,另一个常见的误解不是foreach生成不同的变量,而是lambdas接近值而不是变量。@Servy-对于引用类型,在大多数情况下实际上是一样的。嗯,不,真的,不是。如果你没有r更改变量的值以引用另一个对象(就像从未将值类型更改为另一个值一样),关闭变量或值并不重要。除非变量是只读的(或者至少在创建闭包和调用闭包之间没有更改)有一点不同。此外,这是处理值类型,而不是引用类型。
int i;
foreach (i in Enumerable.Range(1, 10))
{
    listActions.Add(() => Console.WriteLine(i));
}