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

C# 在lambda表达式中使用foreach循环的迭代器变量-为什么失败?,c#,lambda,iterator,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> helloMeth

考虑以下代码:

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

...

}
显然,我希望
“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);
}

您可以通过引入其他变量来解决此问题:

...
foreach (var type in types)
        {
            var t = type;
            var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
            helloMethods.Add(sayHello);
        }
....

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


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

@Eric Lippert灯塔已经点亮。除了安德斯,没有上帝,埃里克是他的先知:)我可以补充一点,这可以让那些精通闭包的人措手不及——Lua,可能还有其他语言,在循环括号内有
类型
。因此,在Lua中,您仍然捕获变量,但每次迭代都是一个新变量。这是在Lua中编程时您一直在使用的东西——但在我用C语言编程的岁月里,我还没有编写一个受益于它的
类型
——的方法——超出了括号的范围。有人问过吗?我甚至没有读过这个问题,但从题目中,我知道答案是:每日捕获的变量问题抬起了它丑陋的头。
...
foreach (var type in types)
        {
            var t = type;
            var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
            helloMethods.Add(sayHello);
        }
....