С# объявление переменных внутри лямбда-выражений

Следующий код выводит 33 вместо 012. Я не понимаю, почему новая переменная loopScopedi не записывается на каждой итерации, а не захватывает одну и ту же переменную.

Action[] actions = new Action[3];

for (int i = 0; i < 3; i++)
{

   actions [i] = () => {int loopScopedi = i; Console.Write (loopScopedi);};
}

foreach (Action a in actions) a();     // 333

Скорее всего, этот код производит 012. Какая разница между двумя?

Action[] actions = new Action[3];

for (int i = 0; i < 3; i++)
{
    int loopScopedi = i;
    actions [i] = () => Console.Write (loopScopedi);
}

foreach (Action a in actions) a();     // 012

Ответ 1

Это называется "доступ к модифицированному закрытию". В принципе, существует только одна переменная i, и все три лямбда ссылаются на нее. В конце одна переменная i была увеличена до 3, поэтому все три действия печатают 3. (Обратите внимание, что int loopScopedi = i в лямбда работает только после вызова лямбды позже.)

Во второй версии вы создаете новую int loopScopedi для каждой итерации и устанавливаете ее на текущее значение i, которое равно 0 и 1 и 2, для каждой итерации.

Вы можете попытаться представить себе инкрустацию лямбда, чтобы более четко увидеть, что происходит:

foreach (Action a in actions)
{
   int loopScopedi = i; // i == 3, since this is after the for loop
   Console.Write(loopScopedi); // always outputs 3
}

Versus:

foreach (Action a in actions)
{
   // normally you could not refer to loopScopedi here, but the lambda lets you
   // you have "captured" a reference to the loopScopedi variables in the lambda
   // there are three loopScopedis that each saved a different value of i
   // at the time that it was allocated
   Console.Write(loopScopedi); // outputs 0, 1, 2
}

Ответ 2

Какая разница между двумя?

Разная область.

В вашем первом цикле вы ссылаетесь на переменную i, которая определена в области операторов цикла for, а во втором цикле вы используете локальную переменную. Выход 333 возникает из-за того, что ваш первый цикл выполняет итерацию 3 раза, и соответственно переменная i увеличивается в 3 раза, а затем, когда вы вызываете действия, все они относятся к одной и той же переменной (i).

Во втором цикле вы используете новую переменную для каждого Action, чтобы вы получили 012.

Ответ 3

Переменные, записанные в лямбда, поднимаются в класс, разделяемый между лямбдой и внешним кодом.

В первом примере i поднимается один раз и используется как с for(), так и со всеми переданными лямбдами. Когда вы достигнете Console.WriteLine, i достигнет 3 из цикла for().

В вашем втором примере для каждого цикла цикла создается новый loopScopedi, поэтому он не подвержен влиянию последующих циклов.

Ответ 4

Это о том, как С# обрабатывает замыкания. В первом примере закрытие не будет выполнено правильно, и вы в конечном итоге будете использовать последнее значение всегда; но во втором примере вы фиксируете текущее значение переменной цикла в заполнителе, а затем используете этот заполнитель; который обеспечивает правильное решение.

И есть различия между тем, как С# захватывает переменную цикла в циклах foreach и для циклов в С# 5.0 и предыдущих версиях - это нарушение.

У меня был (почти) тот же вопрос, и я узнал об этом здесь.