在lambda表达式中使用foreach循环的迭代器变量 - 为什么失败?

use*_*890 23 c# lambda iterator

请考虑以下代码:

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;
    }

...

}
Run Code Online (Sandbox Code Playgroud)

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

Hi Int32
Hi Int32
Hi Int32  

很显然,我希望"Hi String","Hi Single","Hi Int32",但显然并非如此.为什么迭代数组的最后一个元素在所有3种方法中使用而不是在适当的方法中使用?

您将如何重写代码以实现预期目标?

SWe*_*eko 31

欢迎来到闭包和捕获变量的世界:)

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

基本上,它是捕获的循环变量,而不是它的值.要获得您认为应该得到的,请执行以下操作:

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

  • @Eric Lippert灯塔已经点亮. (4认同)
  • 安德斯没有上帝,埃里克是他的先知:) (3认同)

Ant*_*ram 7

作为SWeko引用的博客帖子的简要解释,lambda正在捕获变量,而不是.在foreach循环中,变量在每次迭代时都不是唯一的,相同的变量用于循环的持续时间(当您看到编译器在编译时对foreach执行的扩展时,这更加明显).因此,您在每次迭代期间捕获了相同的变量,并且变量(截至上次迭代)引用了集合的最后一个元素.

更新:在较新版本的语言中(从C#5开始),循环变量在每次迭代时都被认为是新的,因此关闭它不会产生与旧版本(C#4和之前版本)相同的问题.