Schreiben von LINQ-Abfragen in C#
Die meisten Abfragen in der einführenden Dokumentation zu LINQ (Language Integrated Query) wurden mithilfe der deklarativen Abfragesyntax von LINQ geschrieben. Die Abfragesyntax muss jedoch in Methodenaufrufe für die .NET Common Language Runtime (CLR) übersetzt werden, wenn der Code kompiliert wird. Diese Methodenaufrufe rufen die Standardabfrageoperatoren auf, die z.B. folgende Namen haben: Where
, Select
, GroupBy
, Join
, Max
und Average
. Sie können sie direkt mithilfe der Methodensyntax anstatt der Abfragesyntax aufrufen.
Abfragesyntax und Methodensyntax sind semantisch identisch, aber viele Benutzer finden die Abfragesyntax einfacher und leichter zu lesen. Einige Abfragen müssen als Methodenaufrufe ausgedrückt werden. Sie müssen z.B. einen Methodenaufruf verwenden, um eine Abfrage auszudrücken, die die Anzahl der Elemente abruft, die einer angegebenen Bedingung entsprechen. Sie müssen einen Methodenaufruf auch für eine Abfrage verwenden, die das Element abruft, das den Maximalwert in der Quellsequenz hat. In der Referenzdokumentation für die Standardabfrageoperatoren im System.Linq-Namespace wird im Allgemeinen die Methodensyntax verwendet. Daher ist es sinnvoll, sich mit der Verwendung der Methodensyntax in Abfragen und Abfrageausdrücken selbst vertraut zu machen, auch wenn mit dem Schreiben von LINQ-Abfragen erst begonnen wird.
Erweiterungsmethoden von Standardabfrageoperatoren
Im folgenden Beispiel wird ein einfacher Abfrageausdruck und die semantisch äquivalente Abfrage gezeigt, die als methodenbasierte Abfrage geschrieben ist.
class QueryVMethodSyntax
{
static void Main()
{
int[] numbers = { 5, 10, 8, 3, 6, 12};
//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;
//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
foreach (int i in numQuery1)
{
Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
Console.Write(i + " ");
}
// Keep the console open in debug mode.
Console.WriteLine(System.Environment.NewLine);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
/*
Output:
6 8 10 12
6 8 10 12
*/
Die Ausgabe der beiden Beispiele ist identisch. Sie sehen, dass der Typ der Abfragevariable in beiden Formen der gleiche ist: IEnumerable<T>.
Betrachten Sie die methodenbasierte Abfrage genauer, um sie besser zu verstehen. Beachten Sie, dass die where
-Klausel auf der rechten Seite des Ausdrucks jetzt als Instanzmethode des numbers
-Objekts ausgedrückt wird. Sie erinnern sich sicher, dass diese über einen IEnumerable<int>
-Typ verfügt. Wenn Sie mit der generischen IEnumerable<T>-Schnittstelle vertraut sind, wissen Sie, dass sie über keine Where
-Methode verfügt. Wenn Sie jedoch die IntelliSense-Vervollständigungsliste in der Visual Studio IDE aufrufen, sehen Sie nicht nur eine Where
-Methode, sondern viele andere Methoden, z.B. Select
, SelectMany
, Join
und Orderby
. Sie sind alle Standardabfrageoperatoren.
Obwohl es so scheint, als sei IEnumerable<T> neu definiert worden, um diese zusätzlichen Methoden zu enthalten, ist dies tatsächlich nicht der Fall. Die Standardabfrageoperatoren werden als eine neue Methodenart, die als Erweiterungsmethoden bezeichnet werden, implementiert. Erweiterungsmethoden „erweitern“ einen vorhandenen Typ; sie können aufgerufen werden, als wären sie Instanzmethoden für den Typ. Die Standardabfrageoperatoren erweitern IEnumerable<T>, weshalb Sie numbers.Where(...)
schreiben können.
Um mit der Verwendung von LINQ zu beginnen, müssen Sie nur wissen, wie Sie Erweiterungsmethoden in Ihrer Anwendung mithilfe der richtigen using
-Anweisungen in den Geltungsbereich einbinden können. Aus Sicht Ihrer Anwendung sind eine Erweiterungsmethode und eine reguläre Instanzmethode identisch.
Weitere Informationen zu Erweiterungsmethoden finden Sie unter Extension Methods (Erweiterungsmethoden). Weitere Informationen über Standardabfrageoperatoren finden Sie unter Standard Query Operators Overview (C#) (Übersicht über Standardabfrageoperatoren (C#)). Einige LINQ-Anbieter (z. B. LINQ to SQL und LINQ to XML) implementieren ihre eigenen Standardabfrageoperatoren und zusätzliche Erweiterungsmethoden für andere Typen als IEnumerable<T>.
Lambdaausdrücke
Beachten Sie im vorherigen Beispiel, dass der bedingte Ausdruck num % 2 == 0
als Inlineargument an die Where
-Methode übergeben wird: Where(num => num % 2 == 0).
. Dieser Inlineausdruck wird als Lambdaausdruck bezeichnet. Dies ist eine einfache Möglichkeit, Code zu schreiben, der sonst auf einem unpraktischeren Weg als anonyme Methode, generischer Delegat oder Ausdrucksbaumstruktur geschrieben werden müsste. In C# ist der Lambdaoperator =>
, der als „wird zu“ gelesen wird. num
auf der linken Seite des Operators ist die Eingabevariable, die num
im Eingabeausdruck entspricht. Der Compiler kann den Typ von num
ableiten, da er weiß, dass es sich bei numbers
um einen generischen IEnumerable<T>-Typ handelt. Der Text des Lambdaausdrucks entspricht genau dem Ausdruck in der Abfragesyntax, in einem anderen Ausdruck oder in einer Anweisung in C#; er kann Methodenaufrufe und andere komplexe Logik enthalten. Der „Rückgabewert“ ist nur das Ergebnis des Ausdrucks.
Sie müssen Lambdaausdrücke nicht häufig verwenden, wenn Sie mit der Verwendung von LINQ beginnen. Allerdings können bestimme Abfragen nur in der Methodensyntax ausgedrückt werden, und einige von ihnen benötigen Lambdaausdrücke. Wenn Sie sich mit Lambdaausdrücken besser vertraut gemacht haben, werden Sie sehen, dass sie ein leistungsstarkes und flexibles Tool in Ihrer LINQ-Toolbox sind. Weitere Informationen finden Sie unter Lambdaausdrücke.
Zusammensetzbarkeit von Abfragen
Beachten Sie im vorherigen Codebeispiel, dass die OrderBy
-Methode durch Verwendung des Punktoperators auf dem Aufruf von Where
aufgerufen wird. Where
erzeugt eine gefilterte Sequenz, und Orderby
bearbeitet sie anschließend durch Sortierung. Da Abfragen IEnumerable
zurückgeben, erstellen Sie sie in der Methodensyntax durch Verkettung von Methodenaufrufen miteinander. Das führt auch der Compiler im Hintergrund aus, wenn Sie über die Abfragesyntax Abfragen erstellen. Da die Abfragevariable die Ergebnisse der Abfrage nicht speichert, können Sie sie jederzeit ändern oder als Basis für eine neue Abfrage verwenden, sogar, wenn sie bereits ausgeführt wurde.
Die folgenden Beispiele veranschaulichen einige einfache LINQ-Abfragen anhand der oben aufgelisteten Herangehensweisen. Im Allgemeinen gilt die Regel, wann immer möglich (1) zu verwenden, und (2) und (3) wenn nötig.
Hinweis
Diese Abfragen funktionieren auf Grundlage einfacher im Speicher enthaltener Auflistungen; die grundlegende Syntax entspricht jedoch genau der in „LINQ to Entities“ und „LINQ to XML“ verwendeten Syntax.
Beispiel: Abfragesyntax
Die empfohlene Methode, die meisten Abfragen zu schreiben, ist die Verwendung der Abfragesyntax zum Erstellen von Abfrageausdrücken. Im folgenden Beispiel werden drei Abfrageausdrücke gezeigt. Der erste Abfrageausdruck veranschaulicht, wie man Ergebnisse durch Anwenden von Bedingungen mit einer where
-Klausel filtern und einschränken kann. Er gibt alle Elemente in der Quellsequenz zurück, deren Wert größer als 7 oder kleiner als 3 ist. Der zweite Ausdruck veranschaulicht, wie man die zurückgegebenen Ergebnisse sortiert. Der dritte Ausdruck veranschaulicht, wie man Ergebnisse nach einem Schlüssel gruppiert. Diese Abfrage gibt basierend auf dem ersten Buchstaben des Worts zwei Gruppen zurück.
List<int> numbers = new() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// The query variables can also be implicitly typed by using var
// Query #1.
IEnumerable<int> filteringQuery =
from num in numbers
where num < 3 || num > 7
select num;
// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num < 3 || num > 7
orderby num ascending
select num;
// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];
Beachten Sie, dass der Typ der Abfrage IEnumerable<T> ist. Alle diese Abfragen können mithilfe von var
geschrieben werden, wie im folgenden Beispiel gezeigt wird:
var query = from num in numbers...
In allen vorherigen Beispielen werden die Abfragen nicht ausgeführt, bis Sie die Iteration über die Abfragevariable in einer foreach
- oder einer anderen Anweisung durchlaufen haben. Weitere Informationen finden Sie unter Introduction to LINQ Queries (Einführung in LINQ-Abfragen).
Beispiel: Methodensyntax
Einige Abfragevorgänge müssen als Methodenaufruf ausgedrückt werden. Die häufigsten derartigen Methoden sind die, die einzelne numerische Werte zurückgeben, wie z.B. Sum, Max, Min, Average usw. Diese Methoden müssen in einer Abfrage immer zuletzt aufgerufen werden, da sie nur einen einzelnen Wert darstellen und nicht als Quelle für einen zusätzlichen Abfragevorgang dienen können. Im folgenden Beispiel wird ein Methodenaufruf in einem Abfrageausdruck dargestellt:
List<int> numbers1 = new() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
List<int> numbers2 = new() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };
// Query #4.
double average = numbers1.Average();
// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
Wenn die Methode über Aktions- oder Funktionsparameter verfügt, werden diese wie im folgenden Beispiel dargestellt in Form eines Lambdaausdrucks zur Verfügung gestellt:
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
Von den vorherigen Abfragen wird nur die vierte Abfrage sofort ausgeführt. Dies liegt daran, dass es einen einzelnen Wert zurückgibt und keine generische IEnumerable<T>-Auflistung. Die Methode selbst muss foreach
verwenden, um einen Wert zu berechnen.
Alle vorherigen Abfragen können mithilfe von implizierter Typisierung mit var geschrieben werden. Dies wird im folgenden Beispiel gezeigt:
// var is used for convenience in these queries
var average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);
Beispiel: Gemischte Abfrage und Methodensyntax
In diesem Beispiel wird veranschaulicht, wie Sie die Methodensyntax auf die Ergebnisse einer Abfrageklausel anwenden können. Umschließen Sie einfach den Abfrageausdruck mit Klammern, wenden Sie anschließend den Punktoperator an, und rufen Sie die Methode auf. Im folgenden Beispiel wird von der siebten Abfrage die Anzahl der Zahlen zurückgegeben, deren Wert zwischen 3 und 7 liegt. Im Allgemeinen ist es jedoch besser, eine zweite Variable zu verwenden, um das Ergebnis des zweiten Methodenaufrufs zu speichern. Auf diese Weise ist es unwahrscheinlicher, dass dieses Ergebnis mit dem Ergebnis der Abfrage verwechselt wird.
// Query #7.
// Using a query expression with method syntax
int numCount1 = (
from num in numbers1
where num < 3 || num > 7
select num
).Count();
// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
from num in numbers1
where num < 3 || num > 7
select num;
int numCount2 = numbersQuery.Count();
Da Abfrage Nr.7 einen einzelnen Wert und keine Auflistung zurückgibt, wird die Abfrage sofort ausgeführt.
Die vorherige Abfrage kann mithilfe der implizierten Typisierung mit var
wie folgt geschrieben werden:
var numCount = (from num in numbers...
Sie kann folgendermaßen in Methodensyntax geschrieben werden:
var numCount = numbers.Where(n => n < 3 || n > 7).Count();
Sie kann mithilfe der implizierten Typisierung wie folgt geschrieben werden:
int numCount = numbers.Where(n => n < 3 || n > 7).Count();