Comments 43
На Джавескрипте такая же фигня есть. Как-то весьма давно наткнулся на эту фичу так глубоко, что думал уже весь модуль переписать. Если результаты работы замкнутой переменной не видно в одном месте, а тем более если обрабатываются они в разное время то пара веселых деньков вам обеспечено.
Ответил бы неправильно, если бы не знал что вопрос с подвохом. Не ожидал этого в шарпе.
P.S.
Енумератор режет глаз. Впрочем как и энумератор. Используйте русский перечеслитель.
На джаваскрипте до появления let все было еще хуже. А на C# время жизни переменной всегда определялось блоком, а не методом. Вот и ноют иногда :)
Это разные вещи. Перечеслитель привязан к коллекциям, итератор к численному значению.
Это и правда фича. Потому что переменная цикла for — она принципиально общая для разных итераций и изменяемая!
Рассмотрим такой код:
for (int i=0; i<10; i++) {
actions.Add(() => Console.WriteLine(i));
if (i % 2 == 0) i++;
actions.Add(() => Console.WriteLine(i));
}
Какое еще может быть тут поведение? Автоматически создавать новую переменную внутри цикла с тем же именем — нельзя, оператор i++
будет работать не так как ожидается.
Захватывать переменную по значению? Тоже нельзя, они же всегда захватываются по ссылке, за что такое исключение переменным цикла?
Даже создавать копию переменной только для захвата — тоже нельзя! Все из-за оператора i++ в теле цикла.
А уж если вспомнить, что внутри замыкания переменную цикла тоже можно менять — становится ясным, что цикл for может работать только так как он работает сейчас.
А вот с циклом foreach — ситуация другая. Там переменная цикла — неизменяемая, и никаких проблем в перетаскивании ее из внешнего блока во внутренний — нет.
Например, так же, как это сделано для параметров функций — по умолчанию по значению, с ключевым словом ref — по ссылке.
Кстати, недопонял. В этом примере для каждой лямбды будет отдельный внутренний класс? Или обе в одном будут?
Когда вышел C# 5.0, здесь уже была статья, что для цикла foreach поведением изменено таким образом, что на каждой итерации переменная для хранение текущего значения пересоздается (с возможностью через ключ компилятора вернуть старое поведение),
и что для цикла for остается старое поведение — как раз по причине того, что счетчик for принципиально един для всех итераций (по сути — сахар для while с объявленной вне тела цикла переменной-счетчиком).
Но за новую статью — спасибо. Более понятно и наглядно написано, как показалось.
var runInserts = true;
Task.Run(() =>
{
while (runInserts)
{
}
});
...
runInserts = false;
Выше уже неоднократно говорили о том, что различия в поведении замкнутых переменных для for и foreach вызваны различием в семантике обоих циклов.
Помог опыт Go, где есть точно такая же фича и цикл из примера будет работать точно так же.
Как замкнуть переменную в C# и не выстрелить себе в ногу