Использование переменной итератора цикла foreach в лямбда-выражении - почему не получается? - PullRequest
22 голосов
/ 02 июля 2010

Рассмотрим следующий код:

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 методах вместо соответствующего?

Как бы вы переписали код для достижения желаемой цели?

Ответы [ 3 ]

29 голосов
/ 02 июля 2010

Добро пожаловать в мир замыканий и захваченных переменных:)

Эрик Липперт подробно объясняет это поведение:

В основном, захватывается переменная цикла, а не ее значение.Чтобы получить то, что, как вы думаете, вы должны получить, сделайте следующее:

foreach (var type in types)
{
   var newType = type;
   var sayHello = 
            new PrintHelloType(greeting => SayGreetingToType(newType, greeting));
   helloMethods.Add(sayHello);
}
6 голосов
/ 02 июля 2010

В качестве краткого объяснения, которое ссылается на публикации в блоге, на которые ссылается SWeko, лямбда-выражение захватывает переменную , а не значение . В цикле foreach переменная не уникальна для каждой итерации, одна и та же переменная используется для продолжительности цикла (это более очевидно, когда вы видите расширение, которое компилятор выполняет для foreach во время компиляции ). В результате вы захватывали одну и ту же переменную во время каждой итерации, и переменная (по состоянию на последнюю итерацию) ссылается на последний элемент вашего набора.

Обновление: В более новых версиях языка (начиная с C # 5) переменная цикла считается новой с каждой итерацией, поэтому ее закрытие не создает той же проблемы, что и в более старых версиях (C # 4 и ранее).

3 голосов
/ 02 июля 2010

Это можно исправить введя дополнительную переменную:

...
foreach (var type in types)
        {
            var t = type;
            var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
            helloMethods.Add(sayHello);
        }
....
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...