Что-то лучше, чем .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?

4 ответа

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

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

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

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


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

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

aggregationResult.Run();

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

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

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


Лучше избегать функций побочных эффектов, таких как 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.


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

licensed under cc by-sa 3.0 with attribution.