[英]Using the iterator variable of foreach loop in a lambda expression - why fails?
考虑以下代码:
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()
,代码将输出以下意外响应:
Hi Int32 Hi Int32 Hi Int32
显然,我希望使用"Hi String"
, "Hi Single"
, "Hi Int32"
,但显然并非如此。 为什么在所有3种方法中都使用了迭代数组的最后一个元素而不是适当的方法?
您将如何重写代码以实现期望的目标?
欢迎来到闭包和捕获变量的世界:)
埃里克·利珀特(Eric Lippert)对这种行为有深入的解释:
基本上,捕获的是循环变量,而不是值。 要获得您认为应该获得的东西,请执行以下操作:
foreach (var type in types)
{
var newType = type;
var sayHello =
new PrintHelloType(greeting => SayGreetingToType(newType, greeting));
helloMethods.Add(sayHello);
}
作为暗示SWeko引用的博客文章的简要说明,lambda捕获变量而不是value 。 在foreach循环中, 变量在每次迭代中都不是唯一的,在循环期间使用相同的变量(当您看到编译器在编译时对foreach执行的扩展时,这一点更加明显)。 结果,您在每次迭代中都捕获了相同的变量,并且该变量(截至上次迭代)指的是集合中的最后一个元素。
更新:在较新版本的语言(从C#5开始)中,循环变量在每次迭代中都被视为新变量,因此关闭它不会产生与较早版本(C#4和更低版本)相同的问题。
您可以通过引入其他变量来修复它:
...
foreach (var type in types)
{
var t = type;
var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
helloMethods.Add(sayHello);
}
....
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.