Abfrage einer Auflistung von Objekten

Die Bezeichnung „LINQ to Objects“ bezieht sich auf die direkte Verwendung von LINQ-Abfragen mit einer beliebigen IEnumerable- oder IEnumerable<T>-Auflistung, ohne einen LINQ-Zwischenanbieter oder eine API wie LINQ to SQL oder LINQ to XML zu verwenden. Sie können LINQ zur Abfrage beliebiger aufzählbarer Auflistungen wie List<T>, Array oder Dictionary<TKey,TValue> verwenden. Die Sammlung kann benutzerdefiniert sein oder von einer .NET-API zurückgegeben werden. Im LINQ-Ansatz verfassen Sie einen deklarativen Code, in dem beschrieben wird, was Sie abrufen möchten.

Zudem bieten LINQ-Abfragen drei wesentliche Vorteile gegenüber herkömmlichen foreach-Schleifen:

  • Sie sind präziser und lesbarer, insbesondere beim Filtern mehrerer Bedingungen.
  • Sie bieten mit minimalem Anwendungscode leistungsstarke Filter-, Sortier- und Gruppierungsfunktionen.
  • Sie können mit geringfügigen oder ohne Änderungen zu anderen Datenquellen portiert werden.

Je komplexer der für die Daten durchzuführende Vorgang, desto größer ist im Allgemeinen der Vorteil, den Sie durch die Verwendung von LINQ anstelle der herkömmlichen Iterationsverfahren haben.

In diesem Beispiel wird veranschaulicht, wie eine einfache Abfrage für eine Liste von Student-Objekten ausgeführt wird. Jedes Student-Objekt enthält grundlegende Informationen über den Studenten und eine Liste, die die Ergebnisse des Studenten aus vier Prüfungen darstellt.

Hinweis

Viele andere Beispiele in diesem Abschnitt verwenden dieselbe Student-Klasse und students-Auflistung.

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public GradeLevel? Year { get; set; }
    public List<int> ExamScores { get; set; }

    public Student(string FirstName, string LastName, int ID, GradeLevel Year, List<int> ExamScores)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        this.ID = ID;
        this.Year = Year;
        this.ExamScores = ExamScores;
    }

    public Student(string FirstName, string LastName, int StudentID, List<int>? ExamScores = null)
    {
        this.FirstName = FirstName;
        this.LastName = LastName;
        ID = StudentID;
        this.ExamScores = ExamScores ?? [];
    }

    public static List<Student> students =
    [
        new(
            FirstName: "Terry", LastName: "Adams", ID: 120,
            Year: GradeLevel.SecondYear,
            ExamScores: [99, 82, 81, 79]
        ),
        new(
            "Fadi", "Fakhouri", 116,
            GradeLevel.ThirdYear,
            [99, 86, 90, 94]
        ),
        new(
            "Hanying", "Feng", 117,
            GradeLevel.FirstYear,
            [93, 92, 80, 87]
        ),
        new(
            "Cesar", "Garcia", 114,
            GradeLevel.FourthYear,
            [97, 89, 85, 82]
        ),
        new(
            "Debra", "Garcia", 115,
            GradeLevel.ThirdYear,
            [35, 72, 91, 70]
        ),
        new(
            "Hugo", "Garcia", 118,
            GradeLevel.SecondYear,
            [92, 90, 83, 78]
        ),
        new(
            "Sven", "Mortensen", 113,
            GradeLevel.FirstYear,
            [88, 94, 65, 91]
        ),
        new(
            "Claire", "O'Donnell", 112,
            GradeLevel.FourthYear,
            [75, 84, 91, 39]
        ),
        new(
            "Svetlana", "Omelchenko", 111,
            GradeLevel.SecondYear,
            [97, 92, 81, 60]
        ),
        new(
            "Lance", "Tucker", 119,
            GradeLevel.ThirdYear,
            [68, 79, 88, 92]
        ),
        new(
            "Michael", "Tucker", 122,
            GradeLevel.FirstYear,
            [94, 92, 91, 91]
        ),
        new(
            "Eugene", "Zabokritski", 121,
            GradeLevel.FourthYear,
            [96, 85, 91, 60]
        )
    ];
}

enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

Beispiel

Die folgende Abfrage gibt die Studenten zurück, die ein Ergebnis von 90 oder höher in ihrer ersten Prüfung erzielt haben.

void QueryHighScores(int exam, int score)
{
    var highScores =
        from student in students
        where student.ExamScores[exam] > score
        select new
        {
            Name = student.FirstName,
            Score = student.ExamScores[exam]
        };

    foreach (var item in highScores)
    {
        Console.WriteLine($"{item.Name,-15}{item.Score}");
    }
}

QueryHighScores(0, 90);

Diese Abfrage wurde absichtlich einfach gehalten, damit Sie damit experimentieren können. Sie können z.B. weitere Bedingungen in der where-Klausel ausprobieren oder eine orderby-Klausel verwenden, um die Ergebnisse zu sortieren.

Klassifizierung von Standardabfrageoperatoren nach der Art der Ausführung

Die LINQ to Objects-Implementierungen des Standardabfrageoperators werden mit einer von zwei möglichen Arten ausgeführt: direkt oder zurückgestellt. Abfrageoperatoren, die die verzögerte Ausführung verwenden, können darüber hinaus in zwei Kategorien unterteilt werden: Streaming und Nicht-Streaming. Wenn Sie wissen, wie die einzelnen Abfrageoperatoren ausgeführt werden, erleichtert dies das Verständnis der Ergebnisse, die Sie von einer Abfrage erhalten. Dies ist insbesondere dann der Fall, wenn die Datenquelle geändert wird oder wenn Sie eine Abfrage auf Grundlage einer anderen Abfrage erstellen. In diesem Thema werden die Standardabfrageoperatoren gemäß ihrer Ausführungsarten klassifiziert.

Direkt

Sofortige Ausführung bedeutet, dass die Datenquelle gelesen und die Operation ein Mal ausgeführt wird. Alle Standardabfrageoperatoren, die ein skalares Ergebnis zurückgeben, werden sofort ausgeführt. Sie können erzwingen, dass eine Abfrage sofort ausgeführt wird, indem Sie die Enumerable.ToList- oder Enumerable.ToArray-Methode verwenden. Die sofortige Ausführung ermöglicht die Wiederverwendung von Abfrageergebnissen, aber keine Wiederverwendung der Abfragedeklaration. Die Ergebnisse werden einmal abgerufen und dann für die zukünftige Verwendung gespeichert.

Zurückgestellt

Zurückgestellte Ausführung bedeutet, dass der Vorgang nicht zum Zeitpunkt im Code ausgeführt wird, an dem die Abfrage deklariert wird. Der Vorgang erfolgt nur, wenn die Abfragevariable aufgezählt wird, z.B. durch Verwendung einer foreach-Anweisung. Dies bedeutet, dass die Ergebnisse der Ausführung der Abfrage vom Inhalt der Datenquelle zum Zeitpunkt der Abfrageausführung, nicht der Abfragedefinition abhängen. Wenn die Abfragevariable mehrfach aufgezählt wird, können die Ergebnisse jedes Mal abweichen. Fast alle Standardabfrageoperatoren, deren Rückgabetyp IEnumerable<T> oder IOrderedEnumerable<TElement> ist, werden verzögert ausgeführt. Die verzögerte Ausführung ermöglicht die Wiederverwendung von Abfragen, da die Abfrage die aktualisierten Daten bei jeder Iteration der Abfrageergebnisse aus der Datenquelle abruft.

Abfrageoperatoren, die die verzögerte Ausführung verwenden, können zusätzlich als Streaming und Nicht-Streaming klassifiziert werden.

Streaming

Streaming-Operatoren müssen nicht alle Quelldaten lesen, bevor sie Elemente liefern. Zum Zeitpunkt der Ausführung führt ein Streaming-Operator seine Operation auf jedem Quellelement aus, während es gelesen wird, und liefert ggf. die Elemente. Ein Streaming-Operator liest weiterhin Quellelemente, bis ein Ergebniselement erzeugt werden kann. Dies bedeutet, dass mehr als ein Quellelement womöglich gelesen werden kann, um ein Ergebniselement zu erzeugen.

Nicht-Streaming

Nicht-Streaming-Operatoren müssen alle Quelldaten lesen, bevor sie ein Ergebniselement liefern können. Vorgänge wie das Sortieren oder Gruppieren fallen unter diese Kategorie. Zum Zeitpunkt der Ausführung lesen Nicht-Streaming-Operatoren alle Quelldaten, fügen sie in eine Datenstruktur ein, führen den Vorgang aus und liefern die Elemente, die sich ergeben.

Klassifizierungstabelle

In der folgenden Tabelle wird jede Standardabfrageoperator-Methode laut der Ausführungsmethode klassifiziert.

Hinweis

Wenn ein Operator in zwei Spalten gekennzeichnet ist, werden zwei Eingabesequenzen in den Vorgang einbezogen, und jede Sequenz wird unterschiedlich ausgewertet. In diesen Fällen ist es immer die erste Sequenz in der Parameterliste, die verzögert und mit der Nicht-Straming-Methode ausgewertet wird.

Standardabfrageoperator Rückgabetyp Sofortige Ausführung Verzögerte Streamingausführung Verzögerte Nicht-Streamingausführung
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Einzelner numerischer Wert X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max Einzelner numerischer Wert, TSource oder TResult? X
Min Einzelner numerischer Wert, TSource oder TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Einzelner numerischer Wert X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray TSource[]-Array X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

Siehe auch