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