捕获的variables在C#中循环

我遇到了一个关于C#的有趣的问题。 我有像下面的代码。

List<Func<int>> actions = new List<Func<int>>(); int variable = 0; while (variable < 5) { actions.Add(() => variable * 2); ++ variable; } foreach (var act in actions) { Console.WriteLine(act.Invoke()); } 

我期望它输出0,2,4,6,8。然而,它实际上输出了5个10。

这似乎是由于所有的行动都指向一个被捕获的variables。 因此,当它们被调用时,它们都具有相同的输出。

有没有办法绕过这个限制让每个动作实例都有自己的捕获variables?

是的 – 在循环中取一个variables的副本:

 while (variable < 5) { int copy = variable; actions.Add(() => copy * 2); ++ variable; } 

你可以把它看作是每次碰到variables声明时C#编译器都会创build一个“新”的局部variables。 事实上,它会创build适当的新的闭包对象,如果你引用多个范围内的variables,它会变得复杂(就实现而言),但它的工作原理:)

请注意,此问题更常见的情况是使用forforeach

 for (int i=0; i < 10; i++) // Just one variable foreach (string x in foo) // And again, despite how it reads out loud 

有关这方面的更多细节,请参阅C#3.0规范的7.14.4.2节, 关于闭包的文章也有更多的例子。

我相信你正在经历的是一个被称为Closure http://en.wikipedia.org/wiki/Closure_(computer_science)的东西。; 你的lamba有一个variables的引用,这个variables的作用域在函数本身之外。 你的lamba不会被解释,直到你调用它,一旦它将得到variables在执行时的值。

在幕后,编译器正在生成一个表示方法调用闭包的类。 它为循环的每次迭代使用闭包类的单个实例。 代码看起来像这样,这使得更容易看出错误发生的原因:

 void Main() { List<Func<int>> actions = new List<Func<int>>(); int variable = 0; var closure = new CompilerGeneratedClosure(); Func<int> anonymousMethodAction = null; while (closure.variable < 5) { if(anonymousMethodAction == null) anonymousMethodAction = new Func<int>(closure.YourAnonymousMethod); //we're re-adding the same function actions.Add(anonymousMethodAction); ++closure.variable; } foreach (var act in actions) { Console.WriteLine(act.Invoke()); } } class CompilerGeneratedClosure { public int variable; public int YourAnonymousMethod() { return this.variable * 2; } } 

这实际上并不是您示例中的编译代码,但是我已经检查了自己的代码,这看起来非常像编译器实际生成的代码。

解决方法是将您需要的值存储在代理variables中,并将该variables捕获。

IE

 while( variable < 5 ) { int copy = variable; actions.Add( () => copy * 2 ); ++variable; } 

是的,您需要在循环中将variable作用域并将其传递给lambdaexpression式:

 List<Func<int>> actions = new List<Func<int>>(); int variable = 0; while (variable < 5) { int variable1 = variable; actions.Add(() => variable1 * 2); ++variable; } foreach (var act in actions) { Console.WriteLine(act.Invoke()); } Console.ReadLine(); 

在multithreading(C# ,.NET 4.0)中也出现了同样的情况。

看下面的代码:

目的是按顺序打印1,2,3,4,5。

 for (int counter = 1; counter <= 5; counter++) { new Thread (() => Console.Write (counter)).Start(); } 

输出很有趣! (这可能就像21334 …)

唯一的解决办法是使用局部variables。

 for (int counter = 1; counter <= 5; counter++) { int localVar= counter; new Thread (() => Console.Write (localVar)).Start(); }