请考虑以下代码:
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种方法中使用而不是在适当的方法中使用?
您如何重写代码以实现预期目标?
答案 0 :(得分:28)
欢迎来到闭包和捕获变量的世界:)
Eric Lippert对这种行为有一个深入的解释:
基本上,它是捕获的循环变量,而不是它的值。 要获得您认为应该得到的东西,请执行以下操作:
foreach (var type in types)
{
var newType = type;
var sayHello =
new PrintHelloType(greeting => SayGreetingToType(newType, greeting));
helloMethods.Add(sayHello);
}
答案 1 :(得分:6)
作为一个简短的解释,暗示了SWeko引用的博客帖子,lambda正在捕获变量,而不是值。在foreach循环中,变量在每次迭代时都不是唯一的,相同的变量用于循环的持续时间(当你看到编译器在编译时在foreach上执行的扩展时,这更加明显时间)。因此,您在每次迭代期间捕获了相同的变量,并且变量(截至上次迭代)引用了集合的最后一个元素。
更新:在较新版本的语言中(从C#5开始),循环变量在每次迭代时都被视为新变量,因此关闭它不会产生与旧版本相同的问题版本(C#4和之前)。
答案 2 :(得分:3)
您可以通过引入其他变量来修复它:
...
foreach (var type in types)
{
var t = type;
var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
helloMethods.Add(sayHello);
}
....