Richtige Linq where-Klauseln

133

Ich schreibe in meinem täglichen Leben eine ganze Menge Linq, aber meistens einfache Aussagen. Ich habe festgestellt, dass es bei der Verwendung von where-Klauseln viele Möglichkeiten gibt, sie zu schreiben, und jede hat, soweit ich das beurteilen kann, die gleichen Ergebnisse. Beispielsweise;

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

Scheint zumindest in Bezug auf die Ergebnisse gleichwertig zu sein:

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

Gibt es also wirklich einen anderen Unterschied als die Syntax? Wenn ja, welcher Stil wird bevorzugt und warum?

AR
quelle
203
Sie haben eine boolesche FatEigenschaft? Das ist einfach gemein.
Bala R
104
@ Bala R: Hey, wenn dein Hund fett ist, ist dein Hund fett.
AR

Antworten:

76

Das zweite ist effizienter, da nur ein Prädikat für jedes Element in der Sammlung ausgewertet werden muss, wobei wie im ersten das erste Prädikat zuerst auf alle Elemente angewendet wird und das Ergebnis (das an dieser Stelle eingegrenzt wird) ist wird für das zweite Prädikat verwendet und so weiter. Die Ergebnisse werden bei jedem Durchgang eingegrenzt, es handelt sich jedoch immer noch um mehrere Durchgänge.

Auch die Verkettung (erste Methode) funktioniert nur, wenn Sie Ihre Prädikate UND-verknüpfen. So etwas x.Age == 10 || x.Fat == truefunktioniert mit Ihrer ersten Methode nicht.

Bala R.
quelle
1
Ketten-ORing-Bedingungen sind mit dieser Erweiterung einigermaßen möglich: albahari.com/nutshell/predicatebuilder.aspx
jahu
142

EDIT: LINQ to Objects verhält sich nicht so, wie ich es erwartet hatte. Vielleicht interessiert Sie auch der Blog-Beitrag, den ich gerade darüber geschrieben habe ...


Sie unterscheiden sich in Bezug auf das, was genannt wird - das erste entspricht:

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

wobei letzteres äquivalent ist zu:

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

Welcher Unterschied das tatsächlich macht, hängt von der Implementierung des WhereAufrufs ab. Wenn es sich um einen SQL-basierten Anbieter handelt, würde ich erwarten, dass die beiden am Ende dasselbe SQL erstellen. Wenn es sich um LINQ to Objects handelt, weist die zweite weniger Indirektionsebenen auf (es sind nur zwei statt vier Iteratoren beteiligt). Ob diese Dereferenzierungsebenen sind signifikant in Bezug auf die Geschwindigkeit ist eine andere Sache.

Normalerweise würde ich mehrere whereKlauseln verwenden, wenn sie das Gefühl haben, signifikant unterschiedliche Bedingungen darzustellen (z. B. eine betrifft einen Teil eines Objekts und eine ist vollständig getrennt), und eine whereKlausel, wenn verschiedene Bedingungen eng miteinander verbunden sind (z. B. einen bestimmten Wert) ist größer als ein Minimum und kleiner als ein Maximum). Grundsätzlich lohnt es sich, die Lesbarkeit zu berücksichtigen, bevor geringfügige Leistungsunterschiede auftreten.

Jon Skeet
quelle
1
@ JonSkeet Vielleicht irre ich mich, aber nach einem kurzen Überblick über die Implementierung von Linq Where bin ich mir nicht sicher. Verschachtelt Wo werden durch eine statische Methode 'CombinePredicates' kombiniert. Die Sammlung wird nur einmal von einem einzelnen Iterator mit dem kombinierten Prädikat iteriert. Natürlich hat das Kombinieren von Funk einen Einfluss auf die Leistung, aber es ist sehr begrenzt. Geht es dir gut
Cybermaxs
@Cybermaxs: Nicht sicher, was genau? Ich habe nie vorgeschlagen, die Sammlung mehr als einmal zu wiederholen.
Jon Skeet
@ JonSkeet ja natürlich, aber am Ende werden alle Prädikate kombiniert und nur ein Iterator ist beteiligt. Schauen Sie sich Enumerable.WhereSelectEnumerableIterator an.
Cybermaxs
Die Seite, auf die Sie verlinkt haben, ist jetzt nicht verfügbar. Könnten Sie bitte den Link aktualisieren, wenn der Artikel noch woanders ist? Vielen Dank.
Asad Saeeduddin
2
@Asad: Aktualisiert. (Mein Blog ist umgezogen.)
Jon Skeet
13

Der erste wird implementiert:

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

Im Gegensatz zu den viel einfacheren (und weitaus schnelleren vermutlich schneller):

// all in one fell swoop
Collection.Where(x => x.Age == 10 && x.Name == "Fido" && x.Fat == true)
user7116
quelle
6
"Weit schneller"? Wir wissen noch nicht einmal, um welche LINQ-Implementierung es sich handelt, daher ist es schwierig, Auswirkungen auf die Leistung zu haben.
Jon Skeet
Im allgemeinen Fall benötigt letzteres nur 1 Schleife. Ein Anbieter könnte das erste Beispiel reduzieren, dies ist jedoch nicht erforderlich.
user7116
2
In der Tat ... aber Sie behaupten, letzteres sei viel schneller. Es ist überhaupt nicht klar, dass es überhaupt deutlich schneller sein wird - schließlich hängt die Bedeutung des Leistungsunterschieds davon ab, wie dieser verwendet wird.
Jon Skeet
1
@ Jon: keine Meinungsverschiedenheit. Wie Sie bemerken, könnte die Realität sein, dass der LINQ-Anbieter nützliche Optimierungstransformationen für den Ausdruck durchführt. Da die zweite Schleife jedoch nur eine Schleife erfordert und von einem booleschen Kurzschluss profitiert, ist es schwer zu verstehen, warum sie im Allgemeinen nicht als "weitaus schneller" bezeichnet werden sollte. Wenn das OP nur 5 Elemente hat, ist mein Punkt strittig.
user7116
11

wenn ich renne

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

und

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

Für meine Kundentabelle wird dieselbe SQL-Abfrage ausgegeben

-- 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)

Bei der Übersetzung in SQL gibt es also keinen Unterschied, und Sie haben bereits in anderen Antworten gesehen, wie sie in Lambda-Ausdrücke konvertiert werden

Muhammad Adeel Zahid
quelle
ok, dann willst du sagen, dass es keinen Leistungseffekt hat, wenn ich eines davon benutze?
Bimal Das
WHERE-Klauseln sind tatsächlich verkettet. Es spielt also keine Rolle, wie Sie es schreiben. Es gibt keinen Leistungsunterschied.
Hastrb
3

Unter der Haube werden die beiden Anweisungen in unterschiedliche Abfragedarstellungen umgewandelt. Je nach QueryProviderdemCollection , könnte dies wegoptimiert oder nicht.

Wenn es sich um einen Linq-to-Object-Aufruf handelt, führen mehrere where-Klauseln zu einer Kette von IEnumerables, die voneinander lesen. Die Verwendung des Einzelklauselformulars trägt hier zur Leistungssteigerung bei.

Wenn der zugrunde liegende Anbieter es in eine SQL-Anweisung übersetzt, stehen die Chancen gut, dass beide Varianten dieselbe Anweisung erstellen.

David Schmitt
quelle