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