← Все вопросы

Замыкание в C#: лямбда в цикле захватывает не то значение переменной

Задан 12 месяцев назад451 просмотров2 ответа
8

Пытаюсь создать список лямбд (анонимных методов) в цикле, каждая должна печатать свой номер. Но когда я их потом вызываю, все печатают одно и то же число! Похоже на какую-то магию с захватом переменных, но не пойму, как это исправить.

var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.WriteLine(i));
}

foreach (var a in actions)
    a();

Я ожидал 0 1 2, а в старом проекте получаю 3 3 3. Что происходит и как сделать, чтобы каждая лямбда помнила своё число?

2 ответа

14
✓ Принятый ответ — помог автору

Это классический эффект замыкания: лямбда захватывает не значение переменной, а саму переменную i. Все три лямбды ссылаются на одну и ту же i. К моменту вызова цикл уже закончился и в i лежит 3 — поэтому 3 3 3.

Исправление — завести новую переменную внутри тела цикла на каждой итерации, тогда у каждой лямбды будет своя копия:

var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    int copy = i;                 // новая переменная на каждой итерации
    actions.Add(() => Console.WriteLine(copy));
}

foreach (var a in actions)
    a();   // теперь 0 1 2

Кстати, в foreach (а не в for) и в C# 5+ для переменной цикла foreach эту проблему уже починили на уровне языка. А вот для классического for с i ловушка осталась — нужна явная копия.

7

Дополню, что именно «захватывается». Компилятор под капотом создаёт скрытый класс, в поле которого лежит переменная i, а лямбды держат ссылку на один общий объект этого класса. Поэтому они и видят одно значение.

Когда ты объявляешь int copy = i; внутри цикла, на каждой итерации создаётся новый такой объект со своей copy — и захваты перестают пересекаться. То же самое можно получить, если переписать for на foreach по диапазону:

foreach (int i in Enumerable.Range(0, 3))
    actions.Add(() => Console.WriteLine(i)); // здесь уже 0 1 2 без копии

Но если остаёшься на for — вариант с локальной копией из принятого ответа самый надёжный.

Ваш ответ

Войдите, чтобы ответить на вопрос.
Поддержать проект