Как сказать лямбда-функции захватывать копию вместо ссылки в C #? - PullRequest
24 голосов
/ 16 января 2009

Я изучаю C # и пытаюсь понять лямбды. В приведенном ниже примере он распечатывается 10 раз.

class Program
{
    delegate void Action();
    static void Main(string[] args)
    {
        List<Action> actions = new List<Action>();

        for (int i = 0; i < 10; ++i )
            actions.Add(()=>Console.WriteLine(i));

        foreach (Action a in actions)
            a();
    }
}

Очевидно, сгенерированный класс за лямбдой хранит ссылку или указатель на переменную int i и присваивает новое значение одной и той же ссылке каждый раз, когда цикл повторяется. Есть ли способ заставить lamda получить копию вместо этого, как синтаксис C ++ 0x

[&](){ ... } // Capture by reference

против

[=](){ ... } // Capture copies

Ответы [ 4 ]

25 голосов
/ 16 января 2009

Компилятор извлекает лямбду и все переменные, захваченные лямбдой, во вложенный класс, сгенерированный компилятором.

После компиляции ваш пример выглядит примерно так:

class Program
{
        delegate void Action();
        static void Main(string[] args)
        {
                List<Action> actions = new List<Action>();

                DisplayClass1 displayClass1 = new DisplayClass1();
                for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i )
                        actions.Add(new Action(displayClass1.Lambda));

                foreach (Action a in actions)
                        a();
        }

        class DisplayClass1
        {
                int i;
                void Lambda()
                {
                        Console.WriteLine(i);
                }
        }
}

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

for (int i = 0; i < 10; ++i)
{
    DisplayClass1 displayClass1 = new DisplayClass1();
    displayClass1.i = i;
    actions.Add(new Action(displayClass1.Lambda));
}
10 голосов
/ 16 января 2009

Единственное решение, которое мне удалось найти, - это сначала сделать локальную копию:

for (int i = 0; i < 10; ++i)
{
    int copy = i;
    actions.Add(() => Console.WriteLine(copy));
}

Но у меня возникают проблемы с пониманием, почему помещение копии внутри цикла for отличается от использования лямбда-захвата i.

9 голосов
/ 16 января 2009

Единственное решение - сделать локальную копию и ссылаться на нее в лямбде. Все переменные в C # (и VB.Net) при доступе в замыкании будут иметь ссылочную семантику против семантики копирования / значения. Нет способа изменить это поведение ни на одном языке.

Примечание. На самом деле он не компилируется как ссылка. Компилятор поднимает переменную в класс замыкания и перенаправляет доступы «i» в поле «i» внутри заданного класса замыкания. Тем не менее, часто проще воспринимать это как эталонную семантику.

2 голосов
/ 16 января 2009

Помните, что лямбда-выражения на самом деле являются только синтаксическим сахаром для анонимных методов.

При этом вы действительно ищете, как анонимные методы используют локальные переменные в родительской области видимости.

Вот ссылка, описывающая это. http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx#4

...