我正试图解决这个问题,这里发生了什么?编译器生成什么样的代码?
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
根据this,将为每次迭代创建一个新的ActionHelper实例。那么在这种情况下,我认为它应该打印1..10。 有人能给我一些编译器在这里做的伪代码吗?
感谢。
答案 0 :(得分:10)
在这一行
listActions.Add(() => Console.WriteLine(i));
捕获变量i
,或者如果您愿意,可以在该变量的内存位置创建指针。这意味着每个委托都有一个指针到该内存位置。执行此循环后:
foreach (int i in Enumerable.Range(1, 10))
{
listActions.Add(() => Console.WriteLine(i));
}
由于显而易见的原因i
为10
,因此Action
(s)中指向的所有指针所指向的内存内容变为10.
换句话说,i
被捕获。
顺便说一句,请注意,根据Eric Lippert的说法,C# 5.0
中的这种“奇怪”行为会被解决。
因此,C# 5.0
您的程序会按预期打印 :
1,2,3,4,5...10
编辑:
找不到Eric Lippert关于主题的帖子,但这是另一个:
答案 1 :(得分:9)
编译器生成什么样的代码?
这听起来像你期待的那样:
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的相同的值。
这是不编译器或运行时中的错误......这是语言设计团队的一个有意识的决定。但是,由于对其工作原理的困惑,他们已经推翻了这个决定并decided to risk a breaking change to make it work more like you expect for C# 5。
答案 2 :(得分:0)
本质上发生的事情是编译器在循环之外声明你的int i
,因此没有为每个动作创建它的新值,每次只引用相同的动作。当你执行代码时,我是10,所以它会打印10个。