Понимание того, как компилятор C# работает с методами LINQ цепочки

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

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

самое очевидное, что нужно сделать, это что-то вроде этого:

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);
}

Я взглянул на IL в отражателе; очевидно, что он отличается для двух методов, но анализ его дальше выходит за рамки моих знаний на данный момент:)

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

2 ответов


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

компилятор фактически не выполняет цепочку-это происходит во время выполнения, через обычную организацию объектов! Здесь гораздо меньше магии, чем может показаться на первый взгляд-Джон Скит!--15-->недавно завершен шаг" где предложение" в своей серии блогов повторная реализация LINQ для объектов. Я бы рекомендовал прочитать это.

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

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

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

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

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

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


  1. первый будет использовать один итератор, второй будет использовать два. То есть, сначала устанавливается трубопровод с одним этапом, Второй будет включать в себя два этапа.

  2. два итератора имеют небольшой недостаток производительности для одного.