Рассмотрите следующий код:
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 методах вместо соответствующего?
Как Вы переписали бы код для достижения желаемой цели?
Добро пожаловать в мир закрытий и захваченных переменных :)
У Эрика Липперта есть подробное объяснение такого поведения:
По сути, захватывается переменная цикла, а не ее значение. Чтобы получить то, что вы думаете, вы должны получить, сделайте следующее:
foreach (var type in types)
{
var newType = type;
var sayHello =
new PrintHelloType(greeting => SayGreetingToType(newType, greeting));
helloMethods.Add(sayHello);
}
В качестве краткого объяснения, которое ссылается на сообщения в блогах, Как указано в SWeko, лямбда захватывает переменную , а не значение . В цикле foreach переменная не уникальна на каждой итерации, одна и та же переменная используется для продолжительности цикла (это более очевидно, когда вы видите расширение, которое компилятор выполняет для foreach во время компиляции. ). В результате вы захватили одну и ту же переменную во время каждой итерации, а переменная (по состоянию на последнюю итерацию) относится к последнему элементу вашего набора.
Обновление: В более новых версиях языка (начиная с C # 5) переменная цикла считается новой с каждой итерацией, поэтому ее закрытие не вызывает той же проблемы, что и в более старых версиях (C # 4 и ранее).
Вы можете исправить это, введя дополнительную переменную:
...
foreach (var type in types)
{
var t = type;
var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting));
helloMethods.Add(sayHello);
}
....