Что-то лучше, чем .ToArray() для принудительного перечисления вывода LINQ

Я работаю с LINQ для объектов и имею функцию, где в некоторых случаях мне нужно изменить базовую коллекцию до вызова Aggregate(...), а затем вернуть ее в исходное состояние до того, как funciton вернет результаты Aggregate(...). Мой текущий код выглядит примерно так:

bool collectionModified = false;
if(collectionNeedsModification)
{
    modifyCollection();
    collectionModified = true;
}

var aggregationResult = from a in
                            (from b in collection
                             where b.SatisfysCondition)
                             .Aggregate(aggregationFunction)
                        select a.NeededValue;

if(collectionModified)
    modifyCollection();

return aggregationResult;

Однако, как написано, если я изменю коллекцию, я получу неправильный результат, потому что я возвращаю коллекцию в исходное состояние до того, как будет перечислено значение aggregationResult, а результаты LINQ будут оценены с ленивом. Моим текущим решением является использование .ToArray() в моем запросе LINQ следующим образом:

var aggregationResult = (from a in
                            (from b in collection
                             where b.SatisfysCondition)
                             .Aggregate(aggregationFunction)
                         select a.NeededValue).ToArray();

Размер результирующего массива всегда будет небольшим (< 100 элементов), поэтому время памяти/обработки не вызывает беспокойства. Является ли это лучшим способом обработки моей проблемы или есть лучший способ принудительно оценить запрос LINQ?

Ответ 1

Просто, чтобы проверить, что я вас понимаю, вы в основном хотите перебирать все результаты, просто чтобы вызвать какие-либо побочные эффекты?

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

foreach (var result in aggregationResult)
{
    // Deliberately empty; simply forcing evaluation of the sequence.
}

В качестве альтернативы вы можете использовать LastOrDefault(), чтобы избежать всего копирования, связанного с ToArray(). Count() будет в порядке, пока результат не реализует IList<T> (что связано с сокращением).

Ответ 2

(Примечание: ввод без компилятора под рукой, поэтому код не проверен)

Если у вас есть Reactive Extensions для .NET как зависимость, вы можете использовать Run():

aggregationResult.Run();

Но для этого может не стоить добавлять зависимость.

Вы также можете реализовать метод Run как метод расширения:

public static MyLinqExtensions 
{
     public static void Run<T>(this IEnumerable<T> e)
     {
         foreach (var _ in e);
     }
}

Ответ 3

Лучше избегать функций побочных эффектов, таких как changeCollection.

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

var modifiedCollection = ModifyCollection(collection, collectionNeedsModification);

var aggregationResult = from a in
                        (from b in modifiedCollection
                         where b.SatisfysCondition)
                         .Aggregate(aggregationFunction)
                    select a.NeededValue;

Где ModifyCollection - это метод, который возвращает измененный набор (или запрос) в параметре в зависимости от логического параметра collectionNeedsModification.

Ответ 4

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