Menu

C# LINQ: Where, Select, OrderBy, GroupBy und mehr

LINQ gibt jeder Collection in C# Abfrageoperatoren. Lerne Methodensyntax und Abfragesyntax, Filtern mit Where, Projizieren mit Select, Sortieren mit OrderBy und ThenBy, Gruppieren mit GroupBy, die Falle zwischen First und FirstOrDefault, Aggregate und wie die verzögerte Ausführung bestimmt, wann deine Abfrage läuft.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

LINQ (Language Integrated Query) ist eine Bibliothek von Methoden zum Filtern, Transformieren, Sortieren, Gruppieren und Zusammenfassen von Sequenzen. Füge using System.Linq; hinzu, und jedes Array, jede List<T>, jedes Dictionary und jedes andere IEnumerable<T> bekommt Methoden wie Where, Select und OrderBy, die jeweils ein Lambda nehmen, das sagt, was mit einem Element passieren soll.

Eine erste Abfrage: Methodensyntax und Abfragesyntax

LINQ hat zwei Schreibweisen. Die Methodensyntax verkettet Aufrufe; die Abfragesyntax liest sich wie SQL. Der Compiler macht aus der Abfragesyntax dieselben Methodenaufrufe, beide liefern also identische Ergebnisse.

Ausgabe:

95, 91, 88
95, 91, 88

Die Methodensyntax ist in modernem Code verbreiteter, und einige Operatoren (Take, Distinct, Count, First, ToList) gibt es nur als Methoden. Die Abfragesyntax glänzt bei Joins und bei Abfragen, die mit let Zwischenvariablen einführen. Der Rest dieser Seite verwendet die Methodensyntax und zeigt die Abfragesyntax dort, wo sie etwas beiträgt.

Where und Select: filtern und projizieren

Where behält die Elemente, die eine Bedingung erfüllen. Select verwandelt jedes Element in etwas anderes. Zusammen decken sie die meisten Alltagsabfragen ab.

Ausgabe:

Mug, Kettle
MUG $8.50 | LAMP $32.00 | KETTLE $27.00 | RUG $120.00
Lamp: 38.40
Rug: 144.00
1. Mug 2. Lamp 3. Kettle 4. Rug

Where ändert nie Elemente, und Select ändert nie ihre Anzahl. new { p.Name, WithTax = ... } erzeugt einen anonymen Typ, eine Klasse, die der Compiler für dich schreibt und deren Properties du mit normaler Punktsyntax liest. Er hat keinen Namen, den du schreiben könntest, eine Variable, die einen enthält, muss also mit var deklariert werden, und er passt zu Ergebnissen, die innerhalb einer Methode bleiben.

OrderBy, ThenBy und Sortieren nach mehreren Schlüsseln

Ausgabe:

Ana 60
Ana 15
Ben 90
Ben 40
Chloe 25
Ben, Ana, Ben, Chloe, Ana

OrderBy ist stabil: Elemente mit gleichen Schlüsseln bleiben in ihrer ursprünglichen Reihenfolge. Nimm ThenBy oder ThenByDescending für einen zweiten Schlüssel. Ein zweites OrderBy würde alles nach seinem eigenen Schlüssel neu sortieren und den ersten Schlüssel nur noch bei Gleichstand entscheiden lassen: die umgekehrte Priorität. In der Abfragesyntax werden die Schlüssel durch Kommas getrennt: orderby o.Customer, o.Total descending.

Anders als List<T>.Sort lässt OrderBy die Quelle unangetastet und gibt eine neue Sequenz zurück.

First, Single, Last und ihre OrDefault-Varianten

Diese geben ein Element statt einer Sequenz zurück. Die einfachen Varianten werfen, wenn es nichts zurückzugeben gibt; die Varianten mit OrDefault geben den Standardwert des Typs zurück.

Ausgabe:

Ana
Ben
Bea
True
First: InvalidOperationException
Single: InvalidOperationException
0

Die Exception von First auf einer leeren Sequenz ist eine InvalidOperationException mit der Meldung „Sequence contains no elements“ (oder „Sequence contains no matching element“, wenn ein Prädikat auf nichts passte), einer der meistgesuchten C#-Fehler. Nimm FirstOrDefault, wenn „keines“ ein normales Ergebnis ist, und prüfe auf null.

Die letzte Zeile zeigt die Falle bei Werttypen: Bei einer int-Sequenz gibt FirstOrDefault 0 zurück, wenn nichts passt, und das lässt sich nicht von einer echten 0 unterscheiden. Filtere mit Where und prüfe zuerst Any(), oder übergib ab .NET 6 deinen eigenen Standardwert: scores.FirstOrDefault(s => s > 100, -1).

Single steht für „genau eines“: Es wirft, wenn es keines oder mehr als eines gibt, und ist damit ebenso eine Zusicherung wie eine Abfrage.

Any, All, Count und Contains

Ausgabe:

True
True
True
3
True
True

Any() hört beim ersten Treffer auf, items.Any() ist also der richtige Weg zu fragen „gibt es überhaupt etwas?“. items.Count() > 0 liest die Property Count, wenn die Quelle eine Collection wie eine Liste oder ein Array ist, durchläuft bei einer Abfrage oder einer anderen verzögerten Sequenz aber zuerst jedes Element. All gibt bei einer leeren Sequenz true zurück, weil es kein Element gibt, das die Bedingung verletzt.

Sum, Average, Min und Max

Ausgabe:

Total: 28.25
Items: 7
Priciest: 7.25
Average price: 4.92
0
Average: InvalidOperationException

Sum einer leeren Sequenz ist 0, aber Average, Min und Max einer leeren Zahlensequenz werfen InvalidOperationException: Es gibt keine sinnvolle Antwort. Sichere mit Any() ab oder nimm vorher DefaultIfEmpty(): empty.DefaultIfEmpty(0).Average().

Distinct, Take und Skip

Ausgabe:

home, shop, cart
Page 1: 1 2 3 4 5 6 7 8 9 10
Page 2: 11 12 13 14 15 16 17 18 19 20
Page 3: 21 22 23
12, 15

Distinct behält das erste Vorkommen jedes Werts in der Reihenfolge. Skip(n).Take(m) ist das Standardmuster für Seitenaufteilung, und TakeWhile/SkipWhile hören beim ersten Element auf oder beginnen dort, das eine Bedingung verletzt. Enumerable.Range(start, count) erzeugt eine Folge von Ganzzahlen ohne Array.

GroupBy

GroupBy sortiert Elemente nach einem Schlüssel in Gruppen. Jede Gruppe hat einen Key und ist selbst eine Sequenz, du kannst sie also aggregieren.

Ausgabe:

North: 3 sales, total 600
South: 2 sales, total 400
East: 1 sales, total 350
Top rep: Ana 400
North -> Ana/Chloe/Ana; South -> Ben/Eli; East -> Dev

Die Gruppen erscheinen in der Reihenfolge, in der ihre Schlüssel zuerst in der Quelle auftauchen (North, South, East), nicht sortiert; ergänze OrderBy(g => g.Key), wenn du das brauchst. In der Abfragesyntax wählt group X by K, was in jede Gruppe kommt (hier nur der Name des Vertreters), und into g benennt die Gruppe, damit die Abfrage weitergehen kann.

ToList, ToArray und ToDictionary

Die Methoden To... führen die Abfrage aus und speichern die Ergebnisse.

Ausgabe:

ben@x.com
2
ToDictionary: ArgumentException

ToDictionary wirft ArgumentException, wenn zwei Elemente denselben Schlüssel erzeugen, genau wie Dictionary.Add. Wenn Duplikate zu erwarten sind, gruppiere zuerst (wie es wordCounts tut) oder nimm ToLookup, das jeden Schlüssel auf eine Sequenz von Werten abbildet.

Verzögerte Ausführung

Where, Select, OrderBy, GroupBy und die meisten anderen Operatoren geben ein Abfrageobjekt zurück und tun nichts, bis etwas es durchläuft. Das hat zwei Folgen, die man gesehen haben sollte:

Ausgabe:

25, 40, 99
  converting 10
  converting 25
  converting 40
  converting 99
Count: 4
  converting 10
  converting 25
  converting 40
  converting 99
Max: 9900
  converting 10
  converting 25
  converting 40
  converting 99
Cached: 4, 9900

Die erste Abfrage hat 99 gesehen, obwohl es erst nach dem Schreiben der Abfrage hinzugefügt wurde: Der Filter lief, als string.Join sie durchlief. Die zweite Abfrage hat ihre Umwandlungen zweimal ausgegeben, weil Count() und Max() sie jeweils von vorn durchlaufen haben. Bei einem billigen Lambda ist das nur verschwendete Arbeit; bei einer Datenbankabfrage, einem Dateizugriff oder einer Zufallszahl im Lambda können die beiden Durchläufe unterschiedliche Daten liefern. Wenn du ein Ergebnis mehr als einmal verwendest, materialisiere es mit ToList() oder ToArray().

Operatoren, die einen einzelnen Wert zurückgeben (Count, Sum, First, Any, Max), und die Methoden To... laufen sofort. Wie diese Verzögerung gebaut ist, steht unter IEnumerable und yield.

Kurzübersicht

OperatorTutGibt zurück
Where(x => cond)FilternSequenz (verzögert)
Select(x => expr)Jedes transformierenSequenz (verzögert)
SelectMany(x => seq)Verschachtelte Sequenzen abflachenSequenz (verzögert)
OrderBy, ThenBy (+Descending)Sortieren, stabilSequenz (verzögert)
GroupBy(x => key)Nach Schlüssel gruppierenSequenz von Gruppen (verzögert)
Distinct, Take, SkipDeduplizieren, SeitenSequenz (verzögert)
First, Single, LastEin Element, wirft wenn keinesElement
FirstOrDefault usw.Ein Element oder StandardwertElement
Any, All, ContainsPrüfenbool
Count, Sum, Min, Max, AverageAggregierenZahl
ToList, ToArray, ToDictionaryAusführen und speichernCollection

Häufige Fehler

  • First() auf einer Sequenz, die leer sein kann. Wirft „Sequence contains no elements“; nimm FirstOrDefault und prüfe.
  • Zwei OrderBy-Aufrufe. Der zweite Schlüssel wird zum Hauptschlüssel; nimm ThenBy.
  • Eine Abfrage mehrmals durchlaufen. Die Arbeit wiederholt sich; rufe einmal ToList() auf.
  • Erwarten, dass eine Abfrage eine Momentaufnahme ihrer Quelle macht. Sie liest die Quelle beim Durchlaufen, nicht beim Schreiben.
  • Average, Min, Max auf einer leeren Sequenz. Sie werfen; sichere mit Any() oder DefaultIfEmpty ab.
  • Count() > 0, um auf Elemente zu prüfen. Any() hört beim ersten auf.

Häufig gestellte Fragen

Was ist LINQ in C#?

LINQ (Language Integrated Query) ist eine Reihe von Erweiterungsmethoden in System.Linq, etwa Where, Select, OrderBy und GroupBy, die mit jedem IEnumerable<T> funktionieren: Arrays, Listen, Dictionaries und Ergebnissen anderer Abfragen. C# hat außerdem eine Abfragesyntax (from x in items where ... select ...), die der Compiler in dieselben Methodenaufrufe übersetzt.

Was ist der Unterschied zwischen Select und Where in LINQ?

Where filtert: Es behält die Elemente, für die das Lambda true zurückgibt, und lässt sie unverändert. Select projiziert: Es verwandelt jedes Element in etwas anderes, etwa eine Property oder ein neues Objekt, und lässt die Anzahl gleich. Oft werden sie verkettet: people.Where(p => p.Age >= 18).Select(p => p.Name).

Was ist der Unterschied zwischen First und FirstOrDefault?

First() gibt das erste Element zurück (oder den ersten Treffer für ein Prädikat) und wirft InvalidOperationException („Sequence contains no elements“ oder „no matching element“), wenn es keines gibt. FirstOrDefault() gibt stattdessen den Standardwert des Typs zurück: null bei Klassen und 0 bei int, prüfe das Ergebnis also vor der Verwendung.

Wie sortiere ich mit LINQ nach zwei Feldern?

Verkette ThenBy nach OrderBy: orders.OrderBy(o => o.Customer).ThenByDescending(o => o.Total). OrderBy zweimal aufzurufen funktioniert nicht: Der zweite Aufruf sortiert die ganze Sequenz neu, sein Schlüssel wird also zum primären, und der erste Schlüssel entscheidet nur noch bei Gleichstand, das Gegenteil des Gemeinten.

Wie funktioniert GroupBy in LINQ?

GroupBy(x => key) gibt eine Gruppe pro eindeutigem Schlüssel zurück. Jede Gruppe ist ein IGrouping<TKey, TElement>: Sie hat eine Property Key und ist selbst eine Sequenz der Elemente mit diesem Schlüssel, du kannst also Count(), Sum() oder Select darauf aufrufen. Die Gruppen erscheinen in der Reihenfolge, in der ihre Schlüssel zuerst auftauchen.

Was ist verzögerte Ausführung in LINQ?

Die meisten LINQ-Operatoren laufen nicht, wenn du sie aufrufst; sie geben eine Abfrage zurück, die bei jedem Durchlaufen läuft (durch foreach, ToList(), Count() und so weiter). Eine Abfrage sieht daher Änderungen an der Quelle, die nach ihrer Definition gemacht wurden, und zweimaliges Durchlaufen erledigt die Arbeit zweimal. Rufe ToList() oder ToArray() auf, um sie einmal auszuführen und die Ergebnisse zu behalten.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S