在lambdaexpression式中使用foreach循环的iteratorvariables – 为什么失败?

考虑下面的代码:

public class MyClass { public delegate string PrintHelloType(string greeting); public void Execute() { Type[] types = new Type[] { typeof(string), typeof(float), typeof(int)}; List<PrintHelloType> helloMethods = new List<PrintHelloType>(); foreach (var type in types) { var sayHello = new PrintHelloType(greeting => SayGreetingToType(type, greeting)); helloMethods.Add(sayHello); } foreach (var helloMethod in helloMethods) { Console.WriteLine(helloMethod("Hi")); } } public string SayGreetingToType(Type type, string greetingText) { return greetingText + " " + type.Name; } ... } 

调用myClass.Execute() ,代码将打印以下意外响应:

嗨Int32
嗨Int32
嗨Int32  

显然,我会期待"Hi String""Hi Single""Hi Int32" ,但显然情况并非如此。 为什么迭代数组的最后一个元素在所有的3个方法中被使用,而不是恰当的?

你将如何重写代码来实现预期的目标?

欢迎来到closures和捕获variables的世界:)

Eric Lippert对此行为进行了深入的解释:

  • closures循环variables被认为是有害的
  • closures循环variables,第二部分

基本上,它是捕获的循环variables,而不是它的价值。 为了得到你认为你应该得到的,做到这一点:

 foreach (var type in types) { var newType = type; var sayHello = new PrintHelloType(greeting => SayGreetingToType(newType, greeting)); helloMethods.Add(sayHello); } 

作为一个简单的解释,暗示了SWeko引用的博客文章,lambda捕获variables ,而不是价值 。 在foreach循环中, variables在每次迭代中都不是唯一的,在循环的持续时间中使用相同的variables(当编译器看到编译器对foreach执行的扩展时,这是更明显的)。 因此,在每次迭代过程中都捕获了相同的variables,variables(最后一次迭代时)引用了您的集合的最后一个元素。

更新:在较新版本的语言中(从C#5开始),循环variables在每次迭代时被认为是新的,所以closures它不会产生与旧版本(C#4和之前的版本)相同的问题。

你可以通过引入额外的variables来解决它:

 ... foreach (var type in types) { var t = type; var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting)); helloMethods.Add(sayHello); } ....