我试图让我的头左右,是什么在这里发生了什么? 该编译器产生什么样的代码?
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。 有人可以给我什么样的编译器在这里做一些伪代码吗?
谢谢。
在这条线
listActions.Add(() => Console.WriteLine(i));
可变i
,被捕获,或者如果希望,创建了一个指针 ,该变量的存储器位置。 这意味着, 每一个代表了一个指向内存位置。 这个循环执行后:
foreach (int i in Enumerable.Range(1, 10))
{
listActions.Add(() => Console.WriteLine(i));
}
出于显而易见的原因i
是10
,使得存在于所有的指针的存储器内容Action
(多个)被指向,变为10。
换句话说, i
被捕获。
顺便说一句,应该注意,根据埃里克利珀,这个“奇怪”的现象将得到解决在C# 5.0
。
因此,在C# 5.0
程序将打印预期 :
1,2,3,4,5...10
编辑:
找不到埃里克利珀对主题的帖子,但这里是另一个问题:
封闭在循环再探
该编译器产生什么样的代码?
这听起来像你想到这个:
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工作更喜欢你 。
在本质上正在发生的事情是,编译器声明你int i
之外循环的,因此不能为每个操作创建它的一个新的价值,只是引用每次相同。 到时候你执行的代码我是10,所以它打印10了很多。