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

发布于 2024-09-07 15:38:44 字数 1058 浏览 2 评论 0原文

考虑以下代码:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

没企图 2024-09-14 15:38:44

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

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

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

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

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:

foreach (var type in types)
{
   var newType = type;
   var sayHello = 
            new PrintHelloType(greeting => SayGreetingToType(newType, greeting));
   helloMethods.Add(sayHello);
}
奶茶白久 2024-09-14 15:38:44

作为暗指 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).

你穿错了嫁妆 2024-09-14 15:38:44

您可以通过引入附加变量来修复它:

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

You can fix it by introducing additional variable:

...
foreach (var type in types)
        {
            var t = type;
            var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
            helloMethods.Add(sayHello);
        }
....
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文