Как вы фиксируете переменные итерации?
когда вы захватываете переменную итерации цикла for, C# обрабатывает эту переменную, как если бы она была объявлена вне цикла. Это означает, что на каждой итерации фиксируется одна и та же переменная. Следующая программа пишет 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
Я читаю C# в двух словах (5-е издание), и сегодня я наткнулся на это, но я не могу понять, почему выход 333
, а не 012
. Это потому, что значение i
это становится печатается значение после цикл? Как это возможно? i
предполагается утилизировать после цикла, не так ли?
3 ответов
переменная 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);
потому что лямбда захватывает последнее значение 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
Я бы рекомендовал вам прочитать в этой статье чтобы лучше понять закрытие
мой подход для понимания 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