Как вы фиксируете переменные итерации?

Когда вы фиксируете переменную итерации цикла for, С# обрабатывает эту переменную, как если бы она была объявлена ​​вне цикла. Это означает, что одна и та же переменная фиксируется на каждой итерации. Следующая программа записывает 333 вместо записи 012:

Action[] actions = new Action[3];
for (int i = 0; i < 3; i++)
    actions [i] = () => Console.Write (i);

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

Я читаю С# в двух словах (5-е издание), и сегодня я наткнулся на это, но я не могу понять, почему вывод 333, а не 012. Это потому, что значение i, которое печатается, является значением после цикла? Как это возможно? i должен быть удален после цикла, не так ли?

Ответ 1

Переменная i фиксируется внутри цикла for, но вы как бы расширяете ее объем, делая это. Таким образом, переменная остается в последнем состоянии, которое равно 3, следовательно, код выводит 333.

Другой способ написать код:

Action[] actions = new Action[3];
int i; //declare i here instead of in the for loop

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

//Now i=3
foreach (Action a in actions) a(); // 333

Результат такой же, как и запись:

Console.Write(i);
Console.Write(i);
Console.Write(i);

Ответ 2

Поскольку лямбда фиксирует последнее значение i, а это 3. Шаг последнего цикла выполняется в последний раз, тогда я становлюсь 3, и ваш цикл завершается.

Я думаю, это поможет вам понять:

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

Console.WriteLine(i);  // writes 3

Вы можете исправить это, используя временную переменную:

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

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

Я бы рекомендовал вам прочитать эту статью, чтобы лучше понять закрытие

Ответ 3

Мой подход к пониманию closure в этом случае состоит в том, чтобы развернуть цикл for:

var actions = new List<Action>();
// this loop is "executed"
for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.Write (i));
}
// pseudo "unroll" the loop
// i = 0
// action(0) = Console.WriteLine(i);
// i = 1
// action(1) = Console.WriteLine(i);
// i = 2
// action(2) = Console.WriteLine(i);
// i = 3

foreach (Action a in actions)
{
    a();
}
// pseudo "unroll" the foreach loop
// a(0) = Console.WriteLine(3); <= last value of i
// a(1) = Console.WriteLine(3); <= last value of i
// a(2) = Console.WriteLine(3); <= last value of i
// thus output is 333

// fix
var actions = new List<Action>();
// this loop is "executed"
for (int i = 0; i < 3; i++)
{
    var temp = i;
    actions.Add(() => Console.Write (temp));
}
// pseudo "unroll"
// i = 0
// temp = 0
// actions(0) => Console.WriteLine(temp); <= temp = 0
// i = 1
// temp = 1
// actions(1) => Console.WriteLine(temp); <= temp = 1
// i = 2
// temp = 2
// actions(2) => Console.WriteLine(temp); <= temp = 2
foreach (Action a in actions)
{
    a();
}
// pseudo "unroll" the foreach loop
// a(0) = Console.WriteLine(0); <= last value of first temp
// a(1) = Console.WriteLine(1); <= last value of second temp
// a(2) = Console.WriteLine(2); <= last value of third temp
// thus 012