Понимание того, как компилятор C # работает с цепочкой методов linq - PullRequest
6 голосов
/ 12 ноября 2010

Я пытаюсь сосредоточиться на том, что делает компилятор C #, когда я создаю цепочку для методов linq, особенно при многократной цепочке одного и того же метода.

Простой пример: Допустим, я пытаюсь отфильтровать последовательность целых чисел на основе двух условий.

Самое очевидное, что нужно сделать, это примерно так:

IEnumerable<int> Method1(IEnumerable<int> input)
{
    return input.Where(i => i % 3 == 0 && i % 5 == 0);
}

Но мы можем также связать методы where с одним условием в каждом:

IEnumerable<int> Method2(IEnumerable<int> input)
{
    return input.Where(i => i % 3 == 0).Where(i => i % 5 == 0);
}

Я посмотрел на ИЛ в отражателе; очевидно, что для этих двух методов они разные, но дальнейший их анализ на данный момент мне неизвестен:)

Хотелось бы узнать:
а) что компилятор делает по-разному в каждом случае и почему.
b) есть ли какие-либо последствия для производительности (не пытаться микрооптимизировать; просто любопытно!)

Ответы [ 2 ]

8 голосов
/ 12 ноября 2010

Ответ на (a) короткий, но я более подробно расскажу ниже:

На самом деле компилятор не выполняет связывание - это происходит во время выполнения, черезнормальная организация объектов!Здесь гораздо меньше волшебства, чем может показаться на первый взгляд - Джон Скит недавно выполнил шаг «Где предложение» в своей серии блогов «Реализация LINQ to Objects».Я бы порекомендовал прочитать это.

В очень короткие сроки происходит следующее: каждый раз, когда вы вызываете метод расширения Where, он возвращает новый объект WhereEnumerable, который имеет две вещи - ссылкуна предыдущую IEnumerable (ту, которую вы назвали Where) и лямбду, которую вы указали.

Когда вы начнете перебирать эту WhereEnumerable (например, в foreach позже вваш код), внутри он просто начинает перебирать IEnumerable, на который ссылается .

"Этот foreach только что попросил у меня следующий элемент в моей последовательности, поэтому я оборачиваюсь и спрашиваю вас о следующем элементе в вашей последовательности".

Это продолжается до тех пор, пока мы не достигнем начала координат, которое на самом деле является неким массивом или хранилищем реальных элементов.Когда каждый Enumerable затем говорит «ОК, вот мой элемент», передавая его обратно по цепочке, он также применяет свою собственную пользовательскую логику.Для Where применяется лямбда-выражение, чтобы проверить, соответствует ли элемент критериям.Если это так, он позволяет перейти к следующему абоненту.Если произойдет сбой, он остановится в этой точке, вернется к своему перечисленному Enumerable и запросит следующий элемент.

Это продолжается до тех пор, пока MoveNext каждого не вернет false, что означает, что перечисление завершено ибольше нет элементов.

Чтобы ответить (b) , есть всегда разница, но здесь это слишком тривиально, чтобы беспокоиться.Не беспокойся об этом:)

1 голос
/ 12 ноября 2010
  1. Первый будет использовать один итератор, второй - два.То есть первый устанавливает конвейер с одним этапом, второй будет состоять из двух этапов.

  2. Два итератора имеют небольшой недостаток производительности, равный одному.

...