在 lambda 表达式中使用 foreach 循环的迭代器变量 - 为什么会失败?
考虑以下代码:
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 个方法中都使用迭代数组的最后一个元素,而不是适当的一个?
您将如何重写代码以实现预期目标?
Consider the following code:
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;
}
...
}
After calling myClass.Execute()
, the code prints the following unexpected response:
Hi Int32 Hi Int32 Hi Int32
Obviously, I would expect "Hi String"
, "Hi Single"
, "Hi Int32"
, but apparently it is not the case. Why the last element of the iterated array is being used in all the 3 methods instead of the appropriate one?
How would you rewrite the code to achieve the desired goal?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
欢迎来到闭包和捕获变量的世界:)
Eric Lippert 对这种行为有深入的解释:
基本上,它是捕获的循环变量,而不是它的价值。
要获得您认为应该获得的内容,请执行以下操作:
Welcome to the world of closures and captured variables :)
Eric Lippert has an in-depth explanation of this behaviour:
basically, it's the loop variable that is captured, not it's value.
To get what you think you should get, do this:
作为暗指 SWeko 引用的博客文章的简要说明,lambda 捕获的是变量,而不是值。在 foreach 循环中,变量在每次迭代中都不是唯一的,在循环期间使用相同的变量(当您看到编译器在编译时对 foreach 执行的扩展时,这一点更加明显时间)。因此,您在每次迭代期间捕获了相同的变量,并且该变量(截至最后一次迭代)引用集合的最后一个元素。
更新:在该语言的较新版本中(从 C# 5 开始),循环变量在每次迭代时都被视为新变量,因此关闭它不会产生与旧版本中相同的问题(C# 4 及之前)。
As a brief explanation that alludes to the blog postings that SWeko referenced, a lambda is capturing the variable, not the value. In a foreach loop, the variable is not unique on each iteration, the same variable is used for the duration of the loop (this is more obvious when you see the expansion the compiler performs on the foreach at compile time). As a result, you've captured the same variable during each iteration, and the variable (as of the last iteration) refers to the last element of your set.
Update: In newer versions of the language (beginning in C# 5), the loop variable is considered new with each iteration, so closing over it does not produce the same problem as it did in older versions (C# 4 and prior).
您可以通过引入附加变量来修复它:
You can fix it by introducing additional variable: