C # под капотом генерация кода для лямбд в циклах - PullRequest
2 голосов
/ 21 января 2011

Этот вопрос основан на ответе одного из моих любимых постеров Мехрдада Афшари в этом вопросе о закрытии.

Мне трудно понять, почему C # генерирует код так, как он делает ......

Вот код вопроса

    static void Main(string[] args)
    {

        List<string> list = new List<string> { "hello world",  "TED", "goodbye world" };

        IEnumerable<string> filteredList1 = list;

        IEnumerable<string> filteredList2 = list;

        var keywords = new[] { "hello", "world" };

        foreach (var keyword in keywords)
        {
            //Will execute the following 
            //filteredList1 = filteredList1.Where(item => item.Contains("hello")).Where(item => item.Contains("world"));;
            string value = keyword;
            filteredList1 = filteredList1.Where(item => item.Contains(value));

            //Will execute the following 
            //filteredList2 = filteredList2.Where(item => item.Contains("world"))
            filteredList2 = filteredList2.Where(item => item.Contains(keyword));
        }

        Console.WriteLine("===================================================");
        Console.WriteLine("LIST 1");
        foreach (var s in filteredList1)  // closure is called here
            Console.WriteLine(s);
        Console.WriteLine("===================================================");

        Console.WriteLine("LIST 2");
        foreach (var s in filteredList2)  // closure is called here
            Console.WriteLine(s);
        Console.WriteLine("===================================================");
    }
}

Дает следующий вывод

===============
LIST 1
hello world
===============
LIST 2
hello world
goodbye world
===============

Моя проблема в том, что я не понимаю, почему в списке FilterList2 не генерируется тот же код, что и в FilterList1. Кажется более разумным, что с каждой итерацией foreach (ключевое слово var в ключевых словах) нужно просто добавить еще один .Where (item => item.Contains (ключевое слово)) и передать текущее значение ключевого слова copy.

Почему он этого не делает?

РЕДАКТИРОВАТЬ : ОК, может быть, я не был ясен. Я понимаю, когда и как возникло замыкание, однако я не понимаю, ПОЧЕМУ это делается так. Конечно, имеет смысл, если компилятор обнаруживает, что используется переменная цикла, тогда почему он не может сгенерировать временную переменную и в конечном итоге оказывается в той же ситуации, что и FilterList1. Я что-то здесь упускаю? Может быть, есть сценарий, когда вы хотите передать один и тот же контекст в лямбду несколько раз, но даже тогда компилятору всегда имеет смысл использовать локальную переменную для хранения значения переменной цикла, когда он используется в качестве контекста для лямбда

Процитируем определение замыкания Джона Скита «Проще говоря, замыкания позволяют вам инкапсулировать некоторое поведение, передавать его, как любой другой объект, и по-прежнему иметь доступ к контексту, в котором они были впервые объявлены».

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

P.S. Прошу прощения, вафля, у меня сильный грипп, и попытка быть лаконичной очень трудна: -)

Ответы [ 2 ]

3 голосов
/ 21 января 2011

Я думаю, что ключ к пониманию двух вещей:

  1. Когда и как генерируется замыкание?В этом случае первое замыкание находится над временной переменной, называемой value, а второе - над переменной цикла
  2. Когда выполняется лямбда, содержащая замыкание?Здесь обе лямбды выполняются в финальных циклах foreach.До сих пор они только были собраны, но не выполнены.Теперь, когда они выполнены, каждый из них проверяет значение замыкания, первый использовал временную переменную, которая отличается для каждого шага цикла foreach (поскольку каждый раз это новая переменная).Второй использует любое значение last переменной цикла foreach, когда оно было построено.
3 голосов
/ 21 января 2011

Вы должны помнить, что классы закрываются по переменным, а не по значениям ... Итак, на каждой итерации для первого фильтра вы фиксируете значение ключевого слова в переменной var 'value', чего вы и ожидали;но для второго фильтра вы фиксируете переменную итерации «ключевое слово», поэтому к моменту ее выполнения все фильтры имеют одинаковое значение для ключевого слова (последнее ключевое слово в итерации) «мир», и он правильно показывает две записикоторые содержат 'world'

. Для объяснения посмотрите следующий вопрос

Почему плохо использовать итерационную переменную в лямбда-выражении

...