Захваченное закрытие (переменная цикла) в С# 5.0

Это работает отлично (как ожидается) в С# 5.0:

var actions = new List<Action>();
foreach (var i in Enumerable.Range(0, 10))
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act();

Отпечатки от 0 до 9. Но этот показывает 10 в 10 раз:

var actions = new List<Action>();
for (var i = 0; i < 10; i++)
{
    actions.Add(() => Console.WriteLine(i));
}
foreach (var act in actions) act();

Вопрос: Это была проблема, которая была у нас в версиях С# до 5.0; поэтому нам пришлось использовать локатор-заполнитель для закрытия и теперь он исправлен - в С# 5.0 - в циклах "foreach". Но не в циклах "для"!

В чем причина этого (не устраняя проблему для циклов for)?

Ответ 1

Каковы причины этого?

Я собираюсь предположить, что вы имеете в виду "почему он не изменился для циклов for?"

Ответ заключается в том, что для циклов for существующее поведение имеет смысл. Если вы разбиваете цикл for на:

  • инициализатор
  • состояние
  • итератора
  • Тело

... тогда цикл примерно равен:

{
    initializer;
    while (condition)
    {
        body;
        iterator;
    }
}

(За исключением того, что iterator выполняется в конце инструкции continue;, конечно.)

Часть инициализации логически происходит только один раз, поэтому вполне логично, что существует только одно "экземпляр переменной". Кроме того, нет никакого естественного "начального" значения переменной на каждой итерации цикла - нечего сказать, что цикл for должен иметь форму, объявляющую переменную в инициализаторе, проверяя ее в состоянии и изменяя ее в итераторе. Что бы вы ожидали от этого цикла?

for (int i = 0, j = 10; i < j; i++)
{
    if (someCondition)
    {
        j++;
    }
    actions.Add(() => Console.WriteLine(i, j));
}

Сравните это с циклом foreach, который выглядит так, будто вы объявляете отдельную переменную для каждой итерации. Heck, переменная доступна только для чтения, что делает ее еще более странной, если подумать, что она является одной переменной, которая изменяется между итерациями. Имеет смысл думать о цикле foreach как о объявлении новой переменной только для чтения на каждой итерации с ее значением, взятым из итератора.