Объект, возвращаемый операторами Linq-to-objects - что происходит под капотом? - PullRequest
1 голос
/ 26 сентября 2011

LINQ использует модель отложенного выполнения, которая означает, что результирующая последовательность не возвращается во время вызова операторов Linq, но вместо этого эти операторы возвращают объект, который затем возвращает элементы последовательности только при перечислении этого объекта.

var results = someCollection.Select(item => item.Foo).Where(foo => foo < 3);

Когда мы перечисляем объект результатов, он будет перебирать someCollection только один раз, и для каждого элемента, запрошенного во время итерации, код (расположенный внутри results объекта) выполняет операцию отображения и, наконец, выполняет фильтрацию.

Но мне трудно понять, что происходит под капотом:

a) Является ли метод Where тем, который фактически создает results объект?

b) Если Where действительно создает results объект, то я предполагаю, что Where нужно также извлечь некоторую логику из оператора Select (например, return Item.Foo), чтобы он мог поместить эту логику в results объект?

в) Если мои предположения верны, как Where может извлечь логику из Select?

d) В любом случае, объект results содержит необходимую логику L для оценки каждого элемента в someCollection. Я предполагаю, что эта логика L не делает никаких дополнительных вызовов операторам Select и Where при оценке каждого элемента в someCollection?

Спасибо


EDIT:

1)

Ваше предположение в г) неверно - результаты просто IEnumerable, который возвращается расширением Where () метод. Только когда вы перебираете перечисление (т.е. используете foreach или ToList ()) будет создана последовательность «по-настоящему». В таком случае - Вы даже можете увидеть это, если вы установите точку останова - все Linq методы расширения выполняются по очереди - метод расширения Where () спросит вход IEnumerable для его первого элемента, который приведет к оператор Select (), в свою очередь, получит первый элемент из лежащей в основе коллекции и выплюнуть элемент FooType.

a) Итак, Where и Select сначала вызываются в операторе присваивания при присваивании результирующего объекта переменной results (var results=...). И затем по очереди Where / Select также вызывается (изнутри объекта results) для каждого элемента при перечислении someCollection?

b) Предполагая, что results экземпляр имеет тип C - когда C класс определен / создан? Это определяется методом Where или класс C определяется компилятором и, таким образом, Where возвращает только экземпляр C?

2)

Только когда вы перебираете перечисление (т.е. с помощью foreach или ToList ()) будет создавать последовательность «по-настоящему». В этот момент вы можно даже увидеть это, если вы установите точку останова - все расширение Linq методы выполняются по очереди - метод расширения Where () спросит входной IEnumerable для его первого элемента, который вызовет Оператор Select () в свою очередь получит первый элемент из базового собирать и выплевывать элемент FooType

a) Вы говорите, что из results объекта Select и Where вызываются для каждого элемента I в коллекции. Предполагая, что I не реализует IEnumerable<>, как тогда можно вызывать Select и Where на I, если они могут работать только на IEnumerable<> типах?

Ответы [ 2 ]

3 голосов
/ 26 сентября 2011

Ключ в том, что все эти методы расширения Linq связаны друг с другом.Каждый работает на выходе предыдущего метода расширения, для Linq to Objects (с другой стороны, Linq to SQL делает некоторые оптимизации), по крайней мере, каждый метод расширения не должен знать ничего другого, кроме непосредственного перечисления, которое является его вводом.

Каждый из этих методов расширения принимает IEnumerable определенного типа в качестве входных данных и возвращает свои результаты снова IEnumerable (возможно, другого типа при использовании Select()).Из-за этого ограничения и цепочки вы можете составлять методы расширения Linq различными способами, что делает Linq таким гибким и мощным.

Так что для вашего примера Select() работает на IEnumerable<YourCollectionType> и дает результаты IEnumerable<FooType>,Where() работает на IEnumerable<FooType> и фильтрует эту последовательность и снова выдает IEnumerable<FooType>.

Ваше предположение в d) неверно - results это просто IEnumerable<FooType>, который возвращается Where() метод расширения.Только когда вы перебираете перечисление (то есть, используя foreach или ToList()), последовательность будет создана "для реального".В этот момент - вы даже можете увидеть это, если установите точку останова - все методы расширения Linq выполняются по очереди - метод расширения Where() будет запрашивать у IEnumerable свой первый элемент, что приведет к Select() оператор в свою очередь получит первый элемент из базовой коллекции и выплюнет FooType элемент.

2 голосов
/ 26 сентября 2011

Думайте об этом так, потому что вот что происходит во время компиляции:

var results = someCollection.Select(item => item.Foo).Where(foo => foo < 3);

переведено на

var results = Enumerable.Where(
                  Enumerable.Select(
                      someCollection, item => item.Foo
                  ),
                  foo => foo < 3
              );

Итак, теперь ясно, что Where работает с результатом Select. Затем Where извлечет из своего источника (в данном случае результат Enumerable.Select) и выдаст по одному элементы из источника, соответствующие предикату (в данном случае foo < 3).

Реализация будет выглядеть примерно так:

public static IEnumerable<T> Where<T>(
    IEnumerable<T> source,
    Func<T, bool> predicate
) {
    foreach(var item in source) {
        if(predicate(item)) {
            yield return item;
        }
    }
}

public static IEnumerable<U> Select<T, U>(
    IEnumerable<T> source,
    Func<T, U> project
) {

    foreach(var item in source) {
        yield project(item);
    }
}

Итак, когда вы хотите извлечь элемент из results, Where будет вытягивать из Select, пока не найдет элемент, соответствующий предикату. Возможно, ему придется вытащить много предметов, пока он не найдет один, чтобы вернуть вам. Между тем, каждый раз, когда он вытягивает из Select, Select вытягивает другой элемент из someCollection и возвращает проекцию (item.Foo). Когда вы пытаетесь вытащить другой предмет из Where, Where вытянет следующий, сколько угодно предметов, начиная с Select, пока не найдет тот, который вам вернется. Если Select исчерпает someCollection в любой момент, Where будет знать, что оно исчерпало запас предметов, и перестанет уступать вам.

...