2011-09-30 3 views
9

Допустим, у меня есть некоторый код:Чтение IEnumerable несколько раз

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20); 
int sum1 = items.Sum(x => x.SomeFlag == true); 

И, например, мне нужно немного другую сумму из коллекции предметов позже в коде.

int sum2 = items.Sum(x => x.OtherFlag == false); 

Так что мой вопрос: Можно ли вызвать методы Linq в IEnumerable более одного раза? Может быть, мне следует вызвать метод Reset() в перечислении или сделать список из элементов, используя метод ToList?

ответ

16

Ну, это действительно зависит от того, что вы хотите сделать. Вы можете взять хит выполнения запроса дважды (и точный смысл того, что будет зависеть от того, что GetAllItems() делает), или вы можете принять удар копирования результатов в список:

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20).ToList(); 

После того, как это в списке, очевидно, что это не проблема перебора по этому списку несколько раз.

Обратите внимание, что вы не можете позвонить Reset, потому что у вас нет итератора - у вас есть IEnumerable<T>. В любом случае я бы не рекомендовал звонить IEnumerator<T> - многие реализации (включая любые сгенерированные компилятором C# из блоков итератора) фактически не реализуют Reset (т. Е. Они генерируют исключение).

1

LINQ использует отложенное выполнение, поэтому «элементы» будут перечисляться только при запросе его с помощью другого метода. Каждый из ваших методов Sum будет принимать O (n) для повторения. В зависимости от того, насколько велик ваш список предметов, вы можете не захотеть повторять его несколько раз.

+0

Это не ответьте на вопрос, который был «Можно ли еще раз вызвать методы Linq для IEnumerable?» ... ответ - нет". –

+0

Ну, что означает «ОК», это немного неясно. Вы можете многократно перебирать список, сколько захотите. Я упоминаю последствия этого в моем ответе. Как это не нормально перебирать их? Отправьте свой собственный ответ, если вы так думаете (хотя уже есть принятый ответ). –

+0

Совершенно ясно. 'items' - это IEnumerable, а не список, и вы можете только переходить его один раз. Нет необходимости публиковать другой ответ, но для читателей важно понять, что это неправильно и даже не затрагивает вопрос. –

3

Я иногда в ситуации, когда мне приходится обрабатывать перечислимые несколько раз. Если перечисление является дорогостоящим, неповторяемым и дает много данных (например, IQueryable, который читает из базы данных), перечисление нескольких раз не является опцией, и не буферизует результат в памяти.

До сегодняшнего дня я часто заканчивал тем, что писал классы агрегатора, в которые я мог нажимать элементы в цикле foreach и в конечном итоге читать результаты - гораздо менее изящный, чем LINQ.

Но подождите, я просто сказал «толчок»? Разве это не звучит ... реактивно? Поэтому я думал во время прогулки по вечерам. Назад домой Я попробовал - и это работает!

В этом примере фрагмент кода показывает, как получить как минимальные и максимальные элементы из последовательности целых чисел в один проход, используя стандартные операторы LINQ (те из Rx, то есть):

public static MinMax GetMinMax(IEnumerable<int> source) 
{ 
    // convert source to an observable that does not enumerate (yet) when subscribed to 
    var connectable = source.ToObservable(Scheduler.Immediate).Publish(); 

    // set up multiple consumers 
    var minimum = connectable.Min(); 
    var maximum = connectable.Max(); 

    // combine into final result 
    var final = minimum.CombineLatest(maximum, (min, max) => new MinMax { Min = min, Max = max }); 

    // make final subscribe to consumers, which in turn subscribe to the connectable observable 
    var resultAsync = final.GetAwaiter(); 

    // now that everybody is listening, enumerate! 
    connectable.Connect(); 

    // result available now 
    return resultAsync.GetResult(); 
} 
Смежные вопросы