Подтвердить что ты не робот

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

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

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", но, по-видимому, это не так. Почему последний элемент итерационного массива используется во всех трех методах вместо подходящего?

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

Вы можете исправить это, введя дополнительную переменную:

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