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
| Operator | Tut | Gibt zurück |
|---|---|---|
Where(x => cond) | Filtern | Sequenz (verzögert) |
Select(x => expr) | Jedes transformieren | Sequenz (verzögert) |
SelectMany(x => seq) | Verschachtelte Sequenzen abflachen | Sequenz (verzögert) |
OrderBy, ThenBy (+Descending) | Sortieren, stabil | Sequenz (verzögert) |
GroupBy(x => key) | Nach Schlüssel gruppieren | Sequenz von Gruppen (verzögert) |
Distinct, Take, Skip | Deduplizieren, Seiten | Sequenz (verzögert) |
First, Single, Last | Ein Element, wirft wenn keines | Element |
FirstOrDefault usw. | Ein Element oder Standardwert | Element |
Any, All, Contains | Prüfen | bool |
Count, Sum, Min, Max, Average | Aggregieren | Zahl |
ToList, ToArray, ToDictionary | Ausführen und speichern | Collection |
Häufige Fehler
First()auf einer Sequenz, die leer sein kann. Wirft „Sequence contains no elements“; nimmFirstOrDefaultund prüfe.- Zwei
OrderBy-Aufrufe. Der zweite Schlüssel wird zum Hauptschlüssel; nimmThenBy. - 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,Maxauf einer leeren Sequenz. Sie werfen; sichere mitAny()oderDefaultIfEmptyab.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.