Einleitung: Das Rätsel, das Anfänger quält
\n\nStellen Sie sich vor: Sie schreiben eine einfache foreach-Schleife in C#, speichern einen Lambda-Ausdruck oder starten eine asynchrone Aufgabe innerhalb der Schleife – und plötzlich erhalten Sie ein völlig unerwartetes Ergebnis. Alle Variablen, die Sie scheinbar sauber "eingefangen" haben, verweisen plötzlich auf das letzte Element der Sammlung. Kommt Ihnen das bekannt vor?
Auf Stack Overflow hat die Frage "Is there a reason for C#'s reuse of the variable in a foreach?" tausende Aufrufe und unzählige Kommentare gesammelt. Das ist nicht nur akademisches Interesse – es ist eine praktische Falle, in die selbst erfahrene Entwickler tappen. In diesem Artikel erklären wir, warum C# das so macht, wie es sich mit den Sprachversionen geändert hat und – am wichtigsten – wie Sie die damit verbundenen Fehler vermeiden.
\n\nWir sprechen in einfacher Sprache, mit Code-Beispielen, damit selbst ein Anfänger in C# das Problem versteht. Los geht's!
\n\nDas Kernproblem: Was bedeutet "Wiederverwendung der Variable"?
\n\nIn der klassischen foreach-Schleife in C# (vor Version 5.0) wurde die Iterationsvariable einmalig für die gesamte Schleife deklariert, nicht für jede Iteration neu erstellt. Das bedeutet: Wenn Sie diese Variable in einem Closure einfangen (z. B. in einem Lambda-Ausdruck oder einer anonymen Methode), verweisen alle Closures auf dieselbe Variable.
Schauen wir uns das klassische Beispiel des Problems an:
\n\nvar actions = new List<Action>();\nvar numbers = new List<int> { 1, 2, 3, 4, 5 };\n\nforeach (var number in numbers)\n{\n actions.Add(() => Console.WriteLine(number));\n}\n\n// Alle Aktionen ausführen\nforeach (var action in actions)\n{\n action();\n}\n\n\nFrage: Was gibt dieser Code aus? Intuitiv würde man 1, 2, 3, 4, 5 erwarten. Tatsächlich würde er (vor C# 5) 5, 5, 5, 5, 5 ausgeben.
\n\nWarum? Weil die Variable number während der gesamten Schleife nur einmal existiert. Wenn die Schleife endet, hat sie den Wert des letzten Elements (5). Alle Lambdas haben eine Referenz auf diese Variable eingefangen, nicht ihren Wert zum Zeitpunkt der Erstellung.
Eine Analogie zum Verständnis
\n\nStellen Sie sich vor, Sie haben eine Kiste (die Variable) und legen nacheinander verschiedene Gegenstände (Werte) hinein. Sie geben Ihren Freunden (den Lambdas) nicht die Gegenstände selbst, sondern den Schlüssel zur Kiste. Wenn Ihre Freunde ihre Gegenstände abholen wollen, öffnen sie die Kiste – und darin liegt der letzte Gegenstand, den Sie hineingelegt haben. So funktioniert das alte foreach.
Historischer Kontext: Warum wurde das so gemacht?
\n\nViele Anfänger fragen: "Das ist doch ein Bug! Warum macht man das?" Tatsächlich war es eine bewusste Entscheidung der C#-Entwickler, basierend auf Leistung und Kompilierungseinfachheit.
\n\n- \n
- Leistung: Eine neue Variable pro Iteration zu erstellen, bedeutet zusätzliche Speicherallokation. Für die meisten einfachen Schleifen ist das nicht kritisch, aber in hochbelasteten Systemen zählt jede Mikrooptimierung. \n
- Konsistenz mit for: In der
for-Schleife wird die Variable ebenfalls nur einmal deklariert. Die Entwickler wollten Einheitlichkeit bewahren. \n - Einfachere Kompilierung: Der alte Ansatz war einfacher im Compiler zu implementieren. Der Preis war jedoch ein nicht offensichtliches Verhalten für Programmierer. \n
Die Community entdeckte dieses "Feature" jedoch schnell und begann, Bug-Reports zu schreiben. Auf Stack Overflow wurde die Frage "Is there a reason for C#'s reuse of the variable in a foreach?" zu einer der beliebtesten. Microsoft hörte zu und änderte das Verhalten mit C# 5.0 (2012).
\n\nWas hat sich in C# 5.0 geändert?
\n\nAb C# 5.0 erstellt der Compiler für jede Iteration der foreach-Schleife eine neue Variable. Das obige Beispiel funktioniert jetzt intuitiv richtig und gibt 1, 2, 3, 4, 5 aus.
// Dies