LINQ + Foreach vs Foreach + If
Мне нужно перебирать список объектов, делая что-то только для объектов с логическим свойством, установленным в true. Я обсуждаю этот код
foreach (RouteParameter parameter in parameters.Where(p => p.Condition))
{ //do something }
и этот код
foreach (RouteParameter parameter in parameters)
{
if !parameter.Condition
continue;
//do something
}
Первый код явно чище, но я подозреваю, что он будет циклически перебирать список дважды - один раз для запроса и один раз для foreach. Это не будет огромный список, поэтому я не слишком беспокоюсь о производительности, но идея цикла дважды меня просто задевает.
Вопрос: Есть ли чистый/симпатичный способ написать это без цикла дважды?
Ответы
Ответ 1
Джон Скит иногда делает демонстрацию LINQ для живых выступлений, чтобы объяснить, как это работает. Представьте, что на сцене у вас три человека. Слева у нас есть один парень, у которого есть перетасованная колода карт. В середине у нас есть один парень, который только проходит по красным карточкам, а справа, у нас есть парень, который хочет карты.
Парень по праву заставляет парня посередине. Парень в середине выкачивает парня слева. Парень на левой руке парень в середине карты. Если он черный, парень в середине бросает его на пол и снова затыкает, пока не получит красную карточку, которую он затем направляет к парню справа. Затем парень по праву снова заставляет парня посередине.
Это продолжается до тех пор, пока у парня слева не останется карт.
Палуба не прошла от начала до конца более одного раза.. Однако, как парень слева, так и парень в середине обработали 52 карты, а парень по правой руке 26 карты. На карточках было всего 52 + 52 + 26 операций, но колода была только зациклирована.
Ваша версия "LINQ" и версия "продолжить" - это одно и то же; если у вас
foreach(var card in deck)
{
if (card.IsBlack) continue;
... use card ...
тогда есть 52 операции, которые извлекают каждую карту из колоды, 52 операции, которые проверяют, чтобы каждая карта была черной, и 26 операций, которые действуют на красной карточке. То же самое точно.
Ответ 2
Большинство операторов Linq, таких как Where
, реализованы для поддержки отложенного и ленивого исполнения. В вашем примере список будет повторяться только один раз, потому что перечислитель, стоящий за IEnumerable, возвращаемый Where
, перечислит список до тех пор, пока не найдет элемент, соответствующий предикату, не даст его и будет продолжаться только тогда, когда ему будет предложено следующее элемент.
С точки зрения кода, я бы предпочел вариант, используя где, хотя можно утверждать, что вы можете объявить локальный для parameters.Where(p => p.Condition)
.
Jon Skeet Edulinq серия настоятельно рекомендуется, чтение некоторых бит этого должно помочь вам с пониманием операторов LINQ.
Ответ 3
Собственно, это не "цикл дважды". Предложение .Where
использует отложенное выполнение. Другими словами, практически не выполняется никакая работа, когда вы вызываете .Where
, но когда вы перебираете результат, он будет перебирать исходный список и пропускать только те элементы, которые соответствуют вашему условию. Если вы думаете об этом с точки зрения того, как выполняется код, вы эффективно это делаете:
Func<Parameter, bool> matchesCondition = p => p.Condition;
foreach(var parameter in parameters)
{
if(matchesCondition(parameter))
{
...
}
}
В качестве стиля я лично предпочитаю нечто большее:
var matchingParameters = parameters.Where(p => p.Condition);
foreach(var parameter in matchingParameters)
{
}
Ответ 4
Я предпочитаю это:
theList.Where(itm => itm.Condition).ToList().ForEach(itmFE => { itmFe.DoSomething(); });