Отложенное выполнение List <T>с использованием Linq - PullRequest
1 голос
/ 17 июня 2010

Скажем, у меня есть List<T> с 1000 предметов в нем.

Затем я передаю это методу, который фильтрует этот список. Когда он проходит через различные случаи (например, может быть 50), List<T> может иметь до 50 различных операций Linq Where(), выполненных над ним.

Я заинтересован в этом беге как можно быстрее. Поэтому я не хочу, чтобы это List<T> фильтровалось каждый раз, когда Where() выполняется на нем.

По сути, мне нужно отложить фактические манипуляции с List<T>, пока не будут применены все фильтры.

Это сделано изначально компилятором? Или просто когда я вызываю .ToList () для IEnumerable, который возвращает List<T>.Where(), или я должен выполнить операции Where() над X (где X = List.AsQueryable ())?

Надеюсь, это имеет смысл.

Ответы [ 3 ]

4 голосов
/ 17 июня 2010

Да , отложенное выполнение поддерживается изначально.Каждый раз, когда вы применяете запрос или лямбда-выражение к вашему списку, запрос сохраняет все выражения, которые выполняются только при вызове .ToList () в запросе.

3 голосов
/ 17 июня 2010

Каждый вызов Where создает новый объект, который знает о вашем фильтре и последовательности, в которой он вызывается.

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

Так что еслиВы звоните Where 50 раз (как в list.Where(...).Where(...).Where(...), в итоге вы получаете что-то, что должно увеличиваться и уменьшаться в стеке вызовов как минимум 50 раз для каждого возвращаемого элемента. Какое влияние это окажет на производительность? Я незнайте: вы должны измерить его.

Одна из возможных альтернатив - построить дерево выражений, а затем скомпилировать его в делегат в конце, и затем вызвать Where.потребуются немного больше усилий, но это может в конечном итоге стать более эффективным. Фактически, это позволит вам изменить это:

list.Where(x => x.SomeValue == 1)
    .Where(x => x.SomethingElse != null)
    .Where(x => x.FinalCondition)
    .ToList()

на

list.Where(x => x.SomeValue == 1 && x.SomethingElse != null && x.FinalCondition)
    .ToList()

Если вы знаете, что вы просто собираетесь объединить множество фильтров "где", это может в конечном итоге оказаться более эффективным, чем проход по IQueryable<T>.Как всегда, проверьте производительность самого простого решения, прежде чем делать что-то более сложное.

0 голосов
/ 17 июня 2010

В вопросе и комментариях столько неудач. Ответы хороши, но не бьют достаточно сильно, чтобы прорваться через неудачу.

Предположим, у вас есть список и запрос.

List<T> source = new List<T>(){  /*10 items*/ };
IEnumerable<T> query = source.Where(filter1);
query = query.Where(filter2);
query = query.Where(filter3);
...
query = query.Where(filter10);

Компилятор выполняет ленивую оценку изначально?

Нет. Ленивая оценка происходит из-за реализации Enumerable.Where

Этот метод реализован с использованием отложенного выполнения. Немедленное возвращаемое значение - это объект, в котором хранится вся информация, необходимая для выполнения действия. Запрос, представленный этим методом, не выполняется до тех пор, пока объект не будет перечислен путем непосредственного вызова его метода GetEnumerator или использования foreach в Visual C # или For Each в Visual Basic.


снижение скорости при вызове List.AsQueryable (). ToList ()

Не звоните AsQueryable, вам нужно только использовать Enumerable.Where.


, таким образом, не предотвратит стек вызовов в 50 вызовов

Глубина стека вызовов гораздо менее важна, чем сначала использование высокоэффективного фильтра. Если вы можете сократить количество элементов раньше, вы уменьшите количество вызовов методов позже.

...