Я встретил интересную проблему о C #. У меня есть код, как показано ниже.
List<Func<int>> actions = new List<Func<int>>();
int variable = 0;
while (variable < 5)
{
actions.Add(() => variable * 2);
++ variable;
}
foreach (var act in actions)
{
Console.WriteLine(act.Invoke());
}
Я ожидаю, что он выведет 0, 2, 4, 6, 8. Однако на самом деле он выдает пять 10 с.
Кажется, что это происходит из-за всех действий, относящихся к одной захваченной переменной. В результате, когда их вызывают, все они имеют одинаковый вывод.
Есть ли способ обойти это ограничение, чтобы каждый экземпляр действия имел свою собственную захваченную переменную?
c#
closures
captured-variable
Морган Ченг
источник
источник
Captured variables are always evaluated when the delegate is actually invoked, not when the variables were captured
,Ответы:
Да - взять копию переменной внутри цикла:
Вы можете думать об этом так, как будто компилятор C # создает «новую» локальную переменную каждый раз, когда попадает в объявление переменной. На самом деле он создаст соответствующие новые объекты замыкания, и он станет сложным (с точки зрения реализации), если вы обращаетесь к переменным в нескольких областях, но это работает :)
Обратите внимание, что более распространенным явлением этой проблемы является использование
for
илиforeach
:См. Раздел 7.14.4.2 спецификации C # 3.0 для более подробной информации, и моя статья о замыканиях также имеет больше примеров.
Обратите внимание, что начиная с компилятора C # 5 и выше (даже при указании более ранней версии C #) поведение
foreach
изменилось, поэтому вам больше не нужно делать локальное копирование. Смотрите этот ответ для более подробной информации.источник
Я считаю, что то, что вы испытываете, называется «Закрытие» http://en.wikipedia.org/wiki/Closure_(computer_science) . Ваша лямба имеет ссылку на переменную, которая выходит за пределы самой функции. Ваша лямба не интерпретируется до тех пор, пока вы ее не вызовете, и, как только она появится, она получит значение, которое переменная имеет во время выполнения.
источник
За кулисами компилятор генерирует класс, который представляет замыкание для вашего вызова метода. Он использует этот единственный экземпляр класса замыкания для каждой итерации цикла. Код выглядит примерно так, что упрощает понимание причины ошибки:
На самом деле это не скомпилированный код из вашего примера, но я изучил свой собственный код, и он очень похож на то, что на самом деле генерирует компилятор.
источник
Способ обойти это - сохранить нужное значение в прокси-переменной и получить эту переменную.
IE
источник
Это не имеет ничего общего с петлями.
Это поведение вызвано тем, что вы используете лямбда-выражение,
() => variable * 2
где внешняя областьvariable
не определена во внутренней области лямбда-выражения .Лямбда-выражения (в C # 3 +, а также анонимные методы в C # 2) по-прежнему создают реальные методы. Передача переменных в эти методы сопряжена с некоторыми дилеммами (передача по значению, передача по ссылке, C # идет по ссылке, но это открывает еще одну проблему, когда ссылка может пережить действительную переменную). Что C # делает, чтобы разрешить все эти дилеммы, так это создать новый вспомогательный класс («замыкание») с полями, соответствующими локальным переменным, используемым в лямбда-выражениях, и методами, соответствующими фактическим лямбда-методам. Любые изменения
variable
в вашем коде фактически переводятся вClosureClass.variable
Таким образом, ваш цикл while продолжает обновлять
ClosureClass.variable
до тех пор, пока он не достигнет 10, тогда цикл for выполняет действия, которые все работают одинаковоClosureClass.variable
.Чтобы получить ожидаемый результат, вам нужно создать разделение между переменной цикла и закрываемой переменной. Вы можете сделать это, введя другую переменную, а именно:
Вы также можете переместить замыкание на другой метод для создания этого разделения:
Вы можете реализовать Mult как лямбда-выражение (неявное закрытие)
или с реальным классом помощника:
В любом случае, «замыкания» не являются концепцией, связанной с циклами , а скорее с анонимными методами / лямбда-выражениями, использующими локальные переменные в области видимости - хотя некоторые неосторожные использования циклов демонстрируют ловушки замыканий.
источник
Да, вам нужно создать область
variable
внутри цикла и передать ее в лямбду следующим образом:источник
Та же самая ситуация происходит в многопоточности (C #, .NET 4.0].
Смотрите следующий код:
Цель - распечатать 1,2,3,4,5 по порядку.
Вывод интересный! (Это может быть как 21334 ...)
Единственное решение - использовать локальные переменные.
источник
Поскольку никто здесь прямо не цитирует ECMA-334 :
Далее в спецификации,
О да, я думаю, следует упомянуть, что в C ++ эта проблема не возникает, потому что вы можете выбрать, будет ли переменная захвачена по значению или по ссылке (см .: лямбда-захват ).
источник
Это называется проблемой закрытия, просто используйте переменную копирования, и все готово.
источник