Це не має нічого спільного з петлями.
Така поведінка викликається тим, що ви використовуєте лямбда-вираз, () => variable * 2
де зовнішній обсяг variable
фактично не визначений у внутрішній області лямбда.
Лямбда-вирази (в C # 3 +, а також анонімні методи в C # 2) все ще створюють фактичні методи. Передача змінних цим методам передбачає деякі дилеми (передавати за значенням? Пройти за посиланням? C # йде з посиланням - але це відкриває ще одну проблему, коли посилання може пережити фактичну змінну). Що C # робить для вирішення всіх цих дилем - це створити новий клас помічників ("закриття") з полями, що відповідають локальним змінним, що використовуються в лямбда-виразах, та методам, відповідним власне лямбда-методам. Будь-які зміни variable
у вашому коді насправді перекладаються на зміниClosureClass.variable
Отже, цикл while продовжує оновлювати до ClosureClass.variable
тих пір, поки він не досягне 10, тоді ви для циклів виконуєте дії, які всі працюють на одній і тій же ClosureClass.variable
.
Щоб отримати очікуваний результат, вам потрібно створити поділ між змінною циклу та змінною, що приховується. Це можна зробити, ввівши іншу змінну, тобто:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
var t = variable; // now t will be closured (i.e. replaced by a field in the new class)
actions.Add(() => t * 2);
++variable; // changing variable won't affect the closured variable t
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Ви також можете перенести закриття на інший метод, щоб створити це розділення:
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(Mult(variable));
++variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Ви можете реалізувати Mult як лямбда-вираз (неявне закриття)
static Func<int> Mult(int i)
{
return () => i * 2;
}
або з фактичним помічником класу:
public class Helper
{
public int _i;
public Helper(int i)
{
_i = i;
}
public int Method()
{
return _i * 2;
}
}
static Func<int> Mult(int i)
{
Helper help = new Helper(i);
return help.Method;
}
У будь-якому випадку, "Замикання" НЕ є поняттям, пов'язаним з циклами , а скоріше з анонімними методами / лямбда-виразами, що використовують локальні масштабовані змінні - хоча деякі обережні використання циклів демонструють пастки закриття.