Это не имеет ничего общего с петлями.
Это поведение вызвано тем, что вы используете лямбда-выражение () => variable * 2
, где внешняя область variable
фактически не определена во внутренней области лямбды.
Лямбда-выражения (в C # 3 +, а также анонимные методы в C # 2) по-прежнему создают фактические методы. Передача переменных в эти методы сопряжена с некоторыми дилеммами (передача по значению, передача по ссылке, C # идет по ссылке, но это открывает еще одну проблему, когда ссылка может пережить действительную переменную). Что C # делает, чтобы разрешить все эти дилеммы, так это создать новый вспомогательный класс («замыкание») с полями, соответствующими локальным переменным, используемым в лямбда-выражениях, и методами, соответствующими фактическим лямбда-методам. Любые изменения в variable
в вашем коде фактически переводятся как изменения в ClosureClass.variable
Таким образом, ваш цикл while продолжает обновлять ClosureClass.variable
до тех пор, пока он не достигнет 10, тогда цикл for выполняет действия, которые все работают с одним и тем же 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;
}
В любом случае, "Замыкания" - это НЕ концепция, связанная с циклами , а скорее с анонимными методами / лямбда-выражениями, использующими локальные переменные области действия - хотя некоторые неосторожные использования циклов демонстрируют ловушки замыканий.