【问题标题】:Using the iterator variable of foreach loop in a lambda expression - why fails?在 lambda 表达式中使用 foreach 循环的迭代器变量 - 为什么会失败?
【发布时间】:2023-03-13 16:08:01
【问题描述】:

考虑以下代码:

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()后,代码打印出以下意外响应:

嗨 Int32 嗨 Int32 嗨 Int32

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

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

【问题讨论】:

标签: c# lambda iterator


【解决方案1】:

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

Eric Lippert 对此行为有深入的解释:

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

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

【讨论】:

  • @Eric Lippert 信标已点亮。
  • 除了安德斯没有上帝,埃里克是他的先知 :)
  • 我可能会补充一点,这甚至可以让那些精通闭包的人措手不及——Lua,可能还有其他语言,在循环括号内有词汇上的type。所以在 Lua 中你仍然可以捕获变量,但是每次迭代它都是一个新变量。这是在 Lua 中编程时你一直在使用的东西 - 但在我多年的 C# 编程中,我还没有编写一个受益于它的方法 type-is-outside-of-the-brackets 范围.有人吗?
【解决方案2】:

你可以通过引入额外的变量来修复它:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2010-09-18
      • 1970-01-01
      • 1970-01-01
      • 2012-03-30
      • 1970-01-01
      • 1970-01-01
      • 2020-01-03
      相关资源
      最近更新 更多