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

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

23
задан Ken Kin 17 July 2013 в 02:44
поделиться

3 ответа

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

У Эрика Липперта есть подробное объяснение такого поведения:

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

foreach (var type in types)
{
   var newType = type;
   var sayHello = 
            new PrintHelloType(greeting => SayGreetingToType(newType, greeting));
   helloMethods.Add(sayHello);
}
31
ответ дан 29 November 2019 в 01:58
поделиться

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

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

7
ответ дан 29 November 2019 в 01:58
поделиться

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

...
foreach (var type in types)
        {
            var t = type;
            var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
            helloMethods.Add(sayHello);
        }
....
4
ответ дан 29 November 2019 в 01:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: