Bei der Verwendung von Lambda-Ausdrücken oder anonymen Methoden in C # müssen wir uns vor dem Zugriff auf modifizierte Abschlussfallen in Acht nehmen. Zum Beispiel:
foreach (var s in strings)
{
query = query.Where(i => i.Prop == s); // access to modified closure
...
}
Aufgrund des geänderten Abschlusses bewirkt der obige Code, dass alle Where
Klauseln in der Abfrage auf dem Endwert von basieren s
.
Wie hier erläutert , geschieht dies, weil die s
in der foreach
obigen Schleife deklarierte Variable im Compiler folgendermaßen übersetzt wird:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
statt so:
while (enumerator.MoveNext())
{
string s;
s = enumerator.Current;
...
}
Wie hier ausgeführt , bietet das Deklarieren einer Variablen außerhalb der Schleife keine Leistungsvorteile. Unter normalen Umständen kann ich mir nur vorstellen, dies zu tun, wenn Sie die Variable außerhalb des Bereichs der Schleife verwenden möchten:
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
...
}
var finalString = s;
In einer foreach
Schleife definierte Variablen können jedoch nicht außerhalb der Schleife verwendet werden:
foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.
Daher deklariert der Compiler die Variable so, dass sie sehr anfällig für Fehler ist, die oft schwer zu finden und zu debuggen sind, ohne dass erkennbare Vorteile entstehen.
Gibt es etwas, das Sie mit foreach
Schleifen auf diese Weise tun können, das Sie nicht könnten, wenn sie mit einer Variablen mit innerem Gültigkeitsbereich kompiliert würden, oder ist dies nur eine willkürliche Auswahl, die getroffen wurde, bevor anonyme Methoden und Lambda-Ausdrücke verfügbar oder allgemein waren und die nicht vorhanden sind wurde seitdem nicht mehr überarbeitet?
String s; foreach (s in strings) { ... }
?foreach
sondern über Lamda-Ausdrücke, die zu einem ähnlichen Code führen, wie er vom OP gezeigt wird ...Antworten:
Ihre Kritik ist völlig berechtigt.
Ich diskutiere dieses Problem hier im Detail:
Das Schließen über der Schleifenvariablen wird als schädlich angesehen
Letzteres. Die C # 1.0-Spezifikation sagte tatsächlich nicht aus, ob sich die Schleifenvariable innerhalb oder außerhalb des Schleifenkörpers befand, da sie keinen beobachtbaren Unterschied machte. Als die Abschlusssemantik in C # 2.0 eingeführt wurde, wurde die Wahl getroffen, die Schleifenvariable außerhalb der Schleife zu platzieren, was mit der "for" -Schleife übereinstimmt.
Ich denke, es ist fair zu sagen, dass alle diese Entscheidung bedauern. Dies ist eine der schlimmsten "Fallstricke" in C #, und wir werden die bahnbrechende Änderung vornehmen , um sie zu beheben. In C # 5 befindet sich die foreach-Schleifenvariable logisch im Hauptteil der Schleife, und daher erhalten Schließungen jedes Mal eine neue Kopie.
Die
for
Schleife wird nicht geändert, und die Änderung wird nicht auf frühere Versionen von C # "zurückportiert". Sie sollten daher weiterhin vorsichtig sein, wenn Sie diese Redewendung verwenden.quelle
foreach
es "sicher"for
ist , aber nicht.Was Sie fragen, wird von Eric Lippert in seinem Blog-Beitrag Closing over the Loop-Variable, die als schädlich angesehen wird, und deren Fortsetzung ausführlich behandelt .
Für mich ist das überzeugendste Argument, dass eine neue Variable in jeder Iteration nicht mit der
for(;;)
Stilschleife vereinbar wäre . Würden Sie erwarten,int i
in jeder Iteration von eine neue zu habenfor (int i = 0; i < 10; i++)
?Das häufigste Problem bei diesem Verhalten ist das Schließen einer Iterationsvariablen, und es gibt eine einfache Problemumgehung:
Mein Blogbeitrag zu diesem Problem: Schließen über jede Variable in C # .
quelle
ref
.cut
für refs / vars undcute
um ausgewertete Werte in teilweise ausgewerteten Abschlüssen zu behalten ).Nachdem ich davon gebissen wurde, habe ich die Angewohnheit, lokal definierte Variablen in den innersten Bereich aufzunehmen, den ich verwende, um sie auf einen Abschluss zu übertragen. In Ihrem Beispiel:
Ich mache:
Sobald Sie diese Angewohnheit haben, können Sie sie in dem sehr seltenen Fall vermeiden, dass Sie tatsächlich beabsichtigten, sich an die äußeren Bereiche zu binden. Um ehrlich zu sein, glaube ich nicht, dass ich das jemals getan habe.
quelle
In C # 5.0 ist dieses Problem behoben, und Sie können Schleifenvariablen schließen und die erwarteten Ergebnisse erzielen.
Die Sprachspezifikation sagt:
quelle