CodeGym /Kurse /C# SELF /Verzögerte Ausführung

Verzögerte Ausführung

C# SELF
Level 34, Lektion 0
Verfügbar

1. Einführung

Stell dir vor: Du hast eine lange, schicke LINQ-Abfrage geschrieben und denkst – „Voilà, jetzt wird alles verarbeitet!“ Und dann merkst du, dass gar nichts passiert, solange du nicht die Elemente zählst oder das Ergebnis in ein Array umwandelst. Das ist kein Bug, sondern ein Feature.

In .NET nutzt LINQ verzögerte Ausführung: Die Abfrage startet nicht, solange du nicht wirklich Daten abfragst. Das ist wie ein fauler Kellner – der rennt nicht in die Küche, bis er hört: „Bring das Essen!“, auch wenn die Bestellung schon aufgenommen ist.

Dieses Verhalten, bekannt als Lazy Evaluation (Lazy Evaluation), macht LINQ besonders effizient beim Arbeiten mit großen, potenziell unendlichen oder ressourcenintensiven Datenquellen.

Verzögerte Ausführung bedeutet, dass eine LINQ-Abfrage nicht sofort nach der Definition ausgeführt wird. Sie startet erst, wenn du wirklich anfängst, die Daten in der Collection zu durchlaufen oder anzuschauen.

Beispiel zur Veranschaulichung


var zahlen = new List<int> { 1, 2, 3, 4, 5 };

// LINQ-Abfrage
var abfrage = zahlen.Where(n =>{
    Console.WriteLine($"Prüfen {n}");
    return n % 2 == 0;
});
Console.WriteLine("Abfrage definiert, aber Zahlen wurden noch nicht geprüft!");

// Erst jetzt beginnt das Durchlaufen!
foreach (var n in abfrage)
{
    Console.WriteLine($"Gerade gefunden: {n}");
}

Was passiert hier?
Wenn du diesen Code ausführst, siehst du, dass vor der foreach-Schleife nichts auf die Konsole ausgegeben wird – nicht mal die Bedingungen in deinem Where werden ausgeführt. Erst wenn du die Elemente durchläufst (z.B. mit foreach), startet die Abfrage.

Das ist genau deferred execution – solange du nicht fragst, macht niemand was!

2. Wozu braucht man verzögerte Ausführung?

Verzögerte Ausführung macht den Code nicht nur eleganter, sondern auch richtig effizient. Warum sollte man Arbeit im Voraus machen, wenn das Ergebnis vielleicht gar nicht gebraucht wird? Das ist besonders wichtig, wenn du mit großen Collections oder Datenströmen arbeitest, die noch ankommen – stell dir eine Quelle vor, die unendlich sein könnte. Alles sofort in den Speicher zu laden, wäre einfach nicht schlau.

Außerdem erlaubt dir deferred execution, Abfragen frei zu kombinieren und zu erweitern. Du kannst komplexe LINQ-Ketten bauen, ohne Angst zu haben, dass sie sofort ausgeführt werden. Alles passiert erst, wenn du wirklich Daten abfragst – und keine Sekunde früher.

Analogie

Verzögerte Ausführung ist wie eine Einkaufsliste auf dem Handy: Du kannst beliebig viele Sachen hinzufügen oder ändern, aber in den Laden gehst du erst, wenn du wirklich was kaufen willst (und erst dann arbeitest du mit der Liste).

3. Wie funktioniert das unter der Haube?

LINQ-Abfragen, die IEnumerable<T> zurückgeben, sind meistens mit Iteratoren (yield return) oder speziellen Lazy-Konstruktionen gebaut. Jedes Mal, wenn du die Collection durchläufst (z.B. mit foreach oder ToList()), startet die Abfrage von vorne.

Wichtige Beobachtung

Wenn du die ursprüngliche Collection zwischen der Definition der Abfrage und ihrer Ausführung änderst, landen die neuen oder geänderten Daten auch im Ergebnis.

Beispiel:


var zahlen = new List<int> { 1, 2, 3 };
var abfrage = zahlen.Where(n => n > 1);
zahlen.Add(4); // neue Zahl hinzugefügt
foreach (var n in abfrage)
{
    Console.WriteLine(n); // gibt 2, 3, 4 aus
}

Diagramm: Wann wird eine LINQ-Abfrage ausgeführt?


flowchart TD
    A[Definition der LINQ-Abfrage] --> B{Abfrage ausführen?}
    B -- Nein --> C[Warten]
    B -- Ja (z.B. foreach, ToList) --> D[Abfrage ausführen]
    D --> E[Ergebnis oder Aktion]
Diagramm für deferred execution: Die Abfrage „wartet“ bis zur echten Nutzung

4. Beispiele, wo die „Faulheit“ von LINQ wichtig ist

Lazy Filterung


var grosseZahlen = Enumerable.Range(1, 1_000_000_000)
    .Where(n => n % 123_456 == 0);

foreach (var n in grosseZahlen.Take(5))
{
    Console.WriteLine(n);
}

Was passiert hier?
Die Abfrage erstellt eine potenziell milliardengroße Collection, aber tatsächlich werden nur 5 Zahlen gefiltert und zurückgegeben! Alles andere wird gar nicht berechnet und belegt keinen Speicher.

Verschachtelte Abfragen und Transaktionen

Angenommen, wir haben eine Liste von Bestellungen und Produkten und wollen die ersten 5 Bestellungen bekommen, die ein bestimmtes Produkt enthalten.


var bestellungen = GetBigOrderList(); // nehmen wir an, hier sind tausende Bestellungen

var gefiltert = bestellungen
    .Where(order => order.Products.Any(p => p.Name == "Kaffee"))
    .Take(5);

foreach(var o in gefiltert)
{
    Console.WriteLine(o.Id);
}

LINQ schaut sich keine weiteren Bestellungen an, wenn schon 5 Treffer gefunden wurden!

5. Überraschungen

Szenarien, in denen deferred execution zu Überraschungen führen kann:

Änderung der Datenquelle nach der Definition der Abfrage

Wie oben gezeigt: Wenn du die ursprüngliche Collection zwischen Definition und Ausführung der Abfrage änderst, sieht die Abfrage die neuen Daten.

Mehrfaches Durchlaufen einer Abfrage

Eine LINQ-Abfrage wird bei jedem Durchlauf neu ausgeführt.


var abfrage = zahlen.Where(n => {
    Console.WriteLine($"Prüfen {n}");
    return n % 2 == 0;
});

foreach(var n in abfrage) {} // erster Durchlauf
foreach(var n in abfrage) {} // zweiter Durchlauf – alles wird nochmal berechnet

Wenn du ein wiederholbares Ergebnis willst – materialisiere es (mach .ToList() oder .ToArray()).

6. Wann sind LINQ-Abfragen NICHT verzögert?

Nicht alle LINQ-Operationen sind verzögert. Manche Methoden führen eine sofortige („gierige“) Ausführung (Immediate Execution) durch. Zum Beispiel:

  • .ToList()
  • .ToArray()
  • .Count()
  • .Average()
  • .Sum()
  • .First(), .Last(), .Single()

Sie alle zwingen LINQ, die Abfrage sofort auszuführen, weil sie kein IEnumerable zurückgeben, sondern ein fertiges Ergebnis.

Beispiel:


var abfrage = zahlen.Where(n => n > 2);
var ergebnis = abfrage.ToList(); // Hier wird die Abfrage sofort ausgeführt!

7. Das Konzept „Lazy Evaluation“ (Lazy Evaluation)

Verzögerte Ausführung (deferred execution) ist ein konkretes Beispiel für Lazy Evaluation (Lazy Evaluation) in .NET.

Lazy Evaluation bedeutet, dass ein Ergebnis erst berechnet wird, wenn es wirklich gebraucht wird. Neben LINQ gibt es in C# noch andere Mechanismen für Lazy Evaluation.

Die Klasse Lazy<T>

C# bietet den speziellen Typ Lazy<T>, um Werte „on demand“ zu erzeugen.

Einfaches Beispiel:


// Wir erstellen eine Lazy-Zahl, die erst berechnet wird, wenn wir sie anfordern
var lazyWert = new Lazy<int>(() =>
{
    Console.WriteLine("Berechne Wert!");
    return 42;
});

Console.WriteLine("Lazy-Objekt erstellt, aber Wert noch nicht berechnet");
Console.WriteLine($"Wert: {lazyWert.Value}"); // hier findet die Berechnung statt

Wozu das Ganze?
Zum Beispiel, wenn du einen Parameter speicherst, der selten gebraucht wird und dessen Berechnung lange dauert oder viele Ressourcen frisst.

8. Tabelle: LINQ-Methoden und Ausführungsmodus

Methode Verzögerte Ausführung Sofortige Ausführung
Where
✅ ❌
Select
✅ ❌
Take
✅ ❌
Skip
✅ ❌
Join
✅ ❌
ToList
❌ ✅
ToArray
❌ ✅
Count
❌ ✅
First
❌ ✅

9. Typische Fehler und Besonderheiten der Implementierung

„Überflüssige Arbeit“ durch wiederholte Durchläufe

Da deferred execution bei jedem Durchlauf neue Berechnungen auslöst, kann es manchmal zu unnötigen Neuberechnungen kommen.


var teureAbfrage = grosseListe.Where(x => SomeHeavyCalculation(x));

var ergebnis1 = teureAbfrage.ToList(); // berechnet – einmal
var ergebnis2 = teureAbfrage.ToList(); // berechnet – zweimal (gleiches Ergebnis, aber nochmal Zeitaufwand)

Die Lösung ist, die Daten einmal zu materialisieren:
var cache = teureAbfrage.ToList();

„Fang den Fehler, wenn du kannst“

Wenn eine Funktion in der Abfrage eine Exception wirft, passiert das erst beim Durchlaufen der Collection, nicht bei der Definition der Abfrage.

Ändern der Collection während des Durchlaufs

Wenn du die Collection während des Durchlaufs änderst, kann das zu einer InvalidOperationException führen.

2
Aufgabe
C# SELF, Level 34, Lektion 0
Gesperrt
Verzögerte Ausführung und mehrfache Abfragen
Verzögerte Ausführung und mehrfache Abfragen
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION