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

Я изучаю 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 и присваивает новое значение той же ссылке каждый раз, когда цикл повторяется. Есть ли способ заставить ламду взять копию вместо этого, например, синтаксис C ++ 0x

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

vs.

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

person Eclipse    schedule 16.01.2009    source источник
comment
Вы можете прочитать эту статью, написанную нашим собственным Джоном Скитом.   -  person Joel Coehoorn    schedule 16.01.2009
comment
возможный дубликат записанной переменной C # в цикле   -  person nawfal    schedule 02.11.2013
comment
Мне кажется любопытным, что большинство ответов на этот вопрос объясняют семантику захвата, которая совершенно ясна автору вопроса, в то время как только некоторые упоминают решение (временная копия). Никто не читает вопросы, прежде чем ответить?   -  person ghord    schedule 29.11.2017


Ответы (4)


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

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

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));
}
person Tinister    schedule 16.01.2009
comment
Спасибо. Это действительно помогло понять, что происходило, когда выражение пыталось оценить параметры. - person Quarkly; 03.12.2018
comment
Без обид на автора, но это не должно быть общепринятым ответом. Вопрос заключался в том, чтобы заставить C # захватывать переменную по значению, и этот ответ объясняет только механизм. Я до сих пор не знаю, как выполнять захват по значению, если только я не использую DisplayClass1 вместо лямбда (что, согласно ИМО, противоречит цели). - person Robert F.; 15.12.2020

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

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

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

person Eclipse    schedule 16.01.2009
comment
Поскольку объявление int находится внутри цикла for, он каждый раз создается заново. Есть 10 разных int, все именованные copy, где есть только один int с именем i, в пределах области, которая становится каррированной. - person technophile; 16.01.2009

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

Примечание: на самом деле он не компилируется как справочник. Компилятор поднимает переменную в класс закрытия и перенаправляет доступ к «i» в поле «i» внутри данного класса закрытия. Однако часто легче думать об этом как об эталонной семантике.

person JaredPar    schedule 16.01.2009

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

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

Вот ссылка с описанием этого. http://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx#4

person Matt Brunell    schedule 16.01.2009
comment
ссылка, описывающая это поведение, является лучшим ответом ИМО, проголосовать - person yanpas; 02.11.2018