С# объявление переменных внутри лямбда-выражений
Следующий код выводит 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 и предыдущих версиях - это нарушение.
У меня был (почти) тот же вопрос, и я узнал об этом здесь.