Правильный Linq, где предложения

133

В повседневной жизни я пишу изрядное количество linq, но в основном это простые операторы. Я заметил, что при использовании предложений where существует множество способов их записи, и каждый из них дает одинаковые результаты, насколько я могу судить. Например;

from x in Collection
  where x.Age == 10
  where x.Name == "Fido"
  where x.Fat == true
  select x;

Похоже, что это эквивалентно, по крайней мере, в том, что касается результатов:

from x in Collection
  where x.Age == 10 &&
        x.Name == "Fido" &&
        x.Fat == true
  select x;

Так есть ли разница, кроме синтаксиса? Если да, то какой стиль предпочтительнее и почему?

Арканзас
источник
203
У вас есть логическое Fatсвойство? Это просто подло.
Bala R
104
@Bala R: Эй, если твоя собака толстая, значит твоя собака толстая.
AR

Ответы:

76

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

Также цепочка (первый метод) будет работать только в том случае, если вы выполняете операцию AND для своих предикатов. Что-то подобное x.Age == 10 || x.Fat == trueне сработает с вашим первым методом.

Бала Р
источник
1
Условия цепочки ORing в некоторой степени возможны с использованием этого расширения: albahari.com/nutshell/predicatebuilder.aspx
jahu
142

РЕДАКТИРОВАТЬ: LINQ to Objects ведет себя не так, как я ожидал. Возможно, вас заинтересует сообщение в блоге, которое я только что написал об этом ...


Они разные с точки зрения того, что будут называться - первый эквивалент:

Collection.Where(x => x.Age == 10)
          .Where(x => x.Name == "Fido")
          .Where(x => x.Fat == true)

где последнее эквивалентно:

Collection.Where(x => x.Age == 10 && 
                      x.Name == "Fido" &&
                      x.Fat == true)

Теперь, какая разница на самом деле зависит от реализации Whereвызова. Если это провайдер на основе SQL, я ожидаю, что они оба создадут один и тот же SQL. Если он находится в LINQ to Objects, второй будет иметь меньше уровней косвенности (будет задействовано только два итератора вместо четырех). Другой вопрос, являются ли эти уровни косвенного обращения значительными с точки зрения скорости.

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

Джон Скит
источник
1
@JonSkeet Может быть, я ошибаюсь, но после быстрого обзора реализации Linq Where я не уверен в этом. Вложенные Where объединены статическим методом CombinePredicates. Коллекция повторяется только один раз одним итератором с комбинированным предикатом. Конечно, комбинирование func влияет на производительность, но оно очень ограничено. С тобой все в порядке ?
Cybermaxs
@Cybermaxs: Не уверен в чем именно? Я никогда не предполагал, что сборник будет повторяться более одного раза.
Джон Скит,
@JonSkeet да, конечно, но в конце объединяются все предикаты и вызывается только один итератор. Посмотрите на Enumerable.WhereSelectEnumerableIterator.
Cybermaxs
Страница, на которую вы ссылались, сейчас не работает. Не могли бы вы обновить ссылку, если статья все еще где-то еще? Спасибо.
Асад Саидуддин
2
@Asad: Обновлено. (Мой блог перемещен.)
Джон Скит,
13

Будет реализовано первое:

Collection.Where(x => x.Age == 10)
          .Where(x => x.Name == "Fido") // applied to the result of the previous
          .Where(x => x.Fat == true)    // applied to the result of the previous

В отличие от гораздо более простого (и, предположительно, гораздо быстрее):

// all in one fell swoop
Collection.Where(x => x.Age == 10 && x.Name == "Fido" && x.Fat == true)
user7116
источник
6
"Намного быстрее"? Мы даже не знаем, какая реализация LINQ задействована, поэтому трудно связать с ней какое-либо влияние на производительность.
Джон Скит
В общем случае для последнего требуется только 1 цикл. Провайдер может решить сгладить первый пример, но это не обязательно.
user7116
2
На самом деле ... но вы утверждая , что последний будет гораздо быстрее. Совершенно не ясно, будет ли он вообще значительно быстрее - в конце концов, значимость разницы в производительности будет зависеть от того, как это будет использоваться.
Джон Скит
1
@Jon: без возражений. Как вы заметили, в действительности поставщик LINQ может выполнять полезные преобразования оптимизации выражения. Но учитывая, что второй требует только один цикл и имеет преимущество от логического короткого замыкания, трудно понять, почему его не следует обозначать как «намного быстрее» в общих чертах. Если OP имеет только 5 элементов, моя точка зрения спорна.
user7116
11

когда я бегу

from c in Customers
where c.CustomerID == 1
where c.CustomerID == 2
where c.CustomerID == 3
select c

и

from c in Customers
where c.CustomerID == 1 &&
c.CustomerID == 2 &&
c.CustomerID == 3
select c customer table in linqpad

против моей таблицы клиентов он выводит тот же запрос sql

-- Region Parameters
DECLARE @p0 Int = 1
DECLARE @p1 Int = 2
DECLARE @p2 Int = 3
-- EndRegion
SELECT [t0].[CustomerID], [t0].[CustomerName]
FROM [Customers] AS [t0]
WHERE ([t0].[CustomerID] = @p0) AND ([t0].[CustomerID] = @p1) AND ([t0].[CustomerID] = @p2)

поэтому в переводе на sql нет разницы, и вы уже видели в других ответах, как они будут преобразованы в лямбда-выражения

Мухаммад Адил Захид
источник
хорошо, тогда вы хотите сказать, что это не повлияет на производительность, если я использую что-либо из этого?
Бимал Дас
Фактически, предложения WHERE связаны цепочкой. Итак, неважно, как вы это напишете. Там нет разницы в производительности.
hastrb
3

Заглянув под капот, два оператора будут преобразованы в разные представления запросов. В зависимости от QueryProviderтого Collection, это может быть оптимизировано или нет.

Когда это вызов linq-to-object, несколько предложений where приведут к цепочке IEnumerables, которые читают друг от друга. Использование формы с одним предложением поможет повысить производительность здесь.

Когда базовый поставщик преобразует его в оператор SQL, велика вероятность того, что оба варианта создадут один и тот же оператор.

Дэвид Шмитт
источник