Typbeziehungen in LINQ-Abfragevorgängen (C#)

Um Abfragen effektiv erstellen zu können, ist es wichtig, dass Sie verstehen, wie die Variablentypen in einer vollständigen Abfrageoperation miteinander zusammenhängen. Wenn Sie diese Beziehungen verstehen, können Sie die LINQ-Beispiele und die Codebeispiele in der Dokumentation besser nachvollziehen. Weiterhin können Sie dann besser verstehen, was passiert, wenn Variablen implizit mithilfe von var typisiert werden.

LINQ-Abfrageoperationen sind in der Datenquelle, in der Abfrage selbst und in der Abfrageausführung stark typisiert. Die Variablentypen in der Abfrage müssen mit den Elementtypen in der Datenquelle und mit dem Typ der Iterationsvariablen in der foreach-Anweisung kompatibel sein. Diese starke Typisierung stellt sicher, dass Typfehler zur Kompilierzeit abgefangen werden, sodass sie korrigiert werden können, bevor die Benutzer sie ausführen.

Um diese Typbeziehungen zu veranschaulichen, wird in den meisten folgenden Beispielen explizite Typisierung für alle Variablen angewendet. Im letzten Beispiel wird gezeigt, wie diese Prinzipien auch dann gelten, wenn Sie die implizite Typisierung mithilfe von var verwenden.

Abfragen, bei denen die Quelldaten nicht transformiert werden

Die folgende Abbildung zeigt eine LINQ to Objects-Abfrageoperation für Objekte, die keine Transformationen der Daten ausführt. Die Quelle enthält eine Sequenz von Zeichenfolgen, und die Abfrageausgabe ist ebenfalls eine Sequenz von Zeichenfolgen.

Diagram that shows the relation of data types in a LINQ query.

  1. Das Typargument der Datenquelle bestimmt den Typ der Bereichsvariablen.
  2. Der Typ des Objekts, das ausgewählt ist, bestimmt den Typ der Abfragevariablen. In diesem Beispiel ist name eine Zeichenfolge. Daher ist die Abfragevariable ein IEnumerable<string>.
  3. Die Abfragevariable durchläuft in der foreach-Anweisung verschiedene Iterationen. Da die Abfragevariable eine Sequenz von Zeichenfolgen ist, ist die Iterationsvariable ebenfalls eine Zeichenfolge.

Abfragen, bei denen die Quelldaten transformiert werden

Die folgende Abbildung zeigt eine LINQ to SQL-Abfrageoperation, die eine einfache Datentransformation ausführt. Die Abfrage verwendet eine Sequenz von Customer-Objekten als Eingabe und wählt nur die Name-Eigenschaft im Ergebnis aus. Da Name eine Zeichenfolge ist, erzeugt die Abfrage eine Sequenz von Zeichenfolgen als Ausgabe.

Diagram showing a query that transforms the data type.

  1. Das Typargument der Datenquelle bestimmt den Typ der Bereichsvariablen.
  2. Die select-Anweisung gibt die Name-Eigenschaft statt des vollständigen Customer-Objekts zurück. Da Name eine Zeichenfolge ist, lautet das Typargument von custNameQuery nicht Customer, sondern string.
  3. Da custNameQuery eine Sequenz von Zeichenfolgen ist, muss die Iterationsvariable der foreach-Schleife auch ein string sein.

Die folgende Abbildung zeigt eine etwas komplexere Transformation. Die select-Anweisung gibt einen anonymen Typ zurück, der nur zwei Member des ursprünglichen Customer-Objekts erfasst.

Diagram showing a more complex query that transforms the data type.

  1. Das Typargument der Datenquelle ist immer der Typ der Bereichsvariablen in der Abfrage.
  2. Da die select-Anweisung einen anonymen Typ erzeugt, muss die Abfragevariable mithilfe von var implizit typisiert werden.
  3. Da der Typ der Abfragevariablen implizit ist, muss die Iterationsvariable in der foreach-Schleife auch implizit sein.

Ableiten von Typinformationen durch den Compiler

Auch wenn Sie die Typbeziehungen einer Abfrageoperation grundsätzlich verstehen sollten, haben Sie die Option, den Compiler alle Arbeitsschritte ausführen zu lassen. Das var-Schlüsselwort kann in einer Abfrageoperation für jede lokale Variable verwendet werden. Die folgende Abbildung ähnelt Beispiel Nummer 2, das zuvor erläutert wurde. Allerdings stellt der Compiler den starken Typ für jede Variable in der Abfrageoperation bereit.

Diagram that shows the type flow with implicit typing.

LINQ und generische Typen (C#)

LINQ-Abfragen basieren auf generischen Typen. Sie benötigen kein ausführliches Wissen über Generics, um Abfragen schreiben zu können. Dennoch sollten Sie zwei grundlegende Konzepte verstehen:

  1. Wenn Sie eine Instanz einer generischen Auflistungsklasse wie etwa List<T> erstellen, ersetzen Sie das „T“ durch den Objekttyp, den die Liste enthalten wird. Eine Liste von Zeichenfolgen wird z.B. als List<string> und eine Liste von Customer-Objekten als List<Customer> ausgedrückt. Eine generische Liste ist stark typisiert und hat gegenüber Auflistungen, die ihre Elemente als Object speichern, viele Vorzüge. Wenn Sie versuchen einen Customer in eine List<string> einzufügen, erhalten Sie zur Laufzeit eine Fehlermeldung. Es ist sehr leicht, generische Auflistungen zu verwenden, da Sie keine Laufzeitumwandlung von Typen durchführen müssen.
  2. IEnumerable<T> ist die Schnittstelle, die es ermöglicht, dass generische Auflistungsklassen mithilfe der Anweisung foreach aufgelistet werden. Generische Auflistungsklassen unterstützen IEnumerable<T>, während nicht generische Auflistungsklassen, wie etwa ArrayList, IEnumerable unterstützen.

Weitere Informationen zu Generics finden Sie unter Generics.

IEnumerable<T>-Variablen in LINQ-Abfragen

LINQ-Abfragevariablen werden als IEnumerable<T> typisiert oder als ein abgeleiteter Typ, etwa IQueryable<T>. Wenn Sie eine Abfragevariable des Typs IEnumerable<Customer> sehen, bedeutet dies nur, dass die Abfrage bei der Ausführung eine Folge von null oder mehr Customer-Objekten produziert.

IEnumerable<Customer> customerQuery =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach (Customer customer in customerQuery)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

Verarbeiten von generischen Typdeklaration durch den Compiler

Falls Sie dies möchten, können Sie generische Syntax umgehen, indem Sie das Schlüsselwort var verwenden. Das Schlüsselwort var weist den Compiler an, den Typ der Abfragevariablen abzuleiten, indem er sich an der in der from-Klausel angegebenen Datenquelle orientiert. Das folgende Beispiel erstellt den gleichen kompilierten Code wie das vorherige Beispiel:

var customerQuery2 =
    from cust in customers
    where cust.City == "London"
    select cust;

foreach(var customer in customerQuery2)
{
    Console.WriteLine($"{customer.LastName}, {customer.FirstName}");
}

Das Schlüsselwort var ist nützlich, wenn der Typ der Variablen offensichtlich ist, oder wenn es nicht so wichtig ist, die geschachtelten generischen Typen explizit festzulegen, wie z.B. diejenigen, die in Gruppenabfragen erstellt werden. Allgemein wird darauf hingewiesen, dass das Verwenden von var das Lesen Ihres Codes durch andere erschwert. Weitere Informationen zu finden Sie unter Implizit typisierte lokale Variablen.