Frage Gibt es einen Grund für C # Wiederverwendung der Variablen in einem foreach?


Bei der Verwendung von Lambda-Ausdrücken oder anonymen Methoden in C # müssen wir vorsichtig sein Zugang zu modifiziertem Verschluss Falle. Beispielsweise:

foreach (var s in strings)
{
   query = query.Where(i => i.Prop == s); // access to modified closure
   ...
}

Aufgrund der modifizierten Schließung wird der obige Code alle verursachen Where Klauseln auf der Abfrage basieren auf dem endgültigen Wert von s.

Wie erklärt Hier, das passiert, weil die s Variable deklariert in foreach Schleife oben wird im Compiler folgendermaßen übersetzt:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

anstatt wie folgt:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

Wie erwähnt Hier, es gibt keine Leistungsvorteile, eine Variable außerhalb der Schleife zu deklarieren, und unter normalen Umständen ist der einzige Grund, den ich dafür denken kann, wenn Sie planen, die Variable außerhalb des Bereichs der Schleife zu verwenden:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

Variablen definiert in a foreach Schleife kann 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 auf eine Weise, die sie sehr anfällig für einen Fehler macht, der oft schwer zu finden und zu debuggen ist, während sie keine wahrnehmbaren Vorteile erzeugt.

Kannst du etwas damit anfangen? foreach Schleifen auf diese Weise, die nicht möglich wären, wenn sie mit einer innerer Gültigkeitsbereich-Variablen kompiliert wurden, oder ist dies nur eine willkürliche Entscheidung, die getroffen wurde, bevor anonyme Methoden und Lambda-Ausdrücke verfügbar oder üblich waren und die seitdem nicht überarbeitet wurden?


1482
2018-01-17 17:21


Ursprung


Antworten:


Der Compiler deklariert die Variable in einer Weise, die sie sehr anfällig für einen Fehler macht, der oft schwer zu finden und zu debuggen ist, während sie keine wahrnehmbaren Vorteile erzeugt.

Ihre Kritik ist völlig berechtigt.

Ich diskutiere dieses Problem im Detail hier:

Schließen der Schleife Variable als schädlich

Gibt es etwas, was Sie mit foreach-Schleifen so machen können, dass Sie nicht könnten, wenn sie mit einer internen Variable kompiliert würden? Oder ist dies nur eine willkürliche Wahl, die getroffen wurde, bevor anonyme Methoden und Lambda-Ausdrücke verfügbar oder üblich waren und die seitdem nicht überarbeitet wurden?

Letzteres. Die C # 1.0-Spezifikation sagte tatsächlich nicht, ob die Schleifenvariable innerhalb oder außerhalb des Schleifenkörpers war, da sie keinen beobachtbaren Unterschied machte. Wenn in C # 2.0 die Closing-Semantik eingeführt wurde, wurde die Wahl getroffen, die Schleifenvariable außerhalb der Schleife zu setzen, und zwar in Übereinstimmung mit der "for" -Schleife.

Ich denke, es ist fair zu sagen, dass alle diese Entscheidung bedauern. Dies ist einer der schlimmsten "Gotchas" in C #, und Wir werden die brechende Veränderung nehmen, um es zu reparieren. In C # 5 wird die foreach-Schleife variabel sein Innerhalb der Körper der Schleife, und daher werden Verschlüsse jedes Mal eine neue Kopie erhalten.

Das for Die Schleife wird nicht geändert, und die Änderung wird nicht auf vorherige Versionen von C # zurück portiert. Sie sollten daher weiterhin vorsichtig sein, wenn Sie dieses Idiom verwenden.


1279
2018-01-17 17:56



Was Sie fragen, wird von Eric Lippert in seinem Blogbeitrag ausführlich behandelt Schließen der Schleife Variable als schädlich und seine Fortsetzung.

Für mich ist das überzeugendste Argument, dass eine neue Variable in jeder Iteration nicht konsistent wäre for(;;)Stil Schleife. Würdest du erwarten, ein neues zu haben? int i in jeder Iteration von for (int i = 0; i < 10; i++)?

Das häufigste Problem bei diesem Verhalten ist das Schließen einer Iterationsvariablen, und es gibt eine einfache Umgehungsmöglichkeit:

foreach (var s in strings)
{
    var s_for_closure = s;
    query = query.Where(i => i.Prop == s_for_closure); // access to modified closure

Mein Blogbeitrag zu diesem Thema: Schließung über foreach Variable in C #.


174
2018-01-17 17:39



Da ich davon gebissen bin, habe ich die Angewohnheit, lokal definierte Variablen in den innersten Bereich einzubeziehen, die ich für die Übertragung an einen beliebigen Abschluss verwende. In Ihrem Beispiel:

foreach (var s in strings)
{
    query = query.Where(i => i.Prop == s); // access to modified closure

Ich mache:

foreach (var s in strings)
{
    string search = s;
    query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.

Sobald Sie diese Gewohnheit haben, können Sie es in der vermeiden sehr seltenen Fall, den Sie tatsächlich an die äußeren Bereiche binden wollten. Um ehrlich zu sein, glaube ich nicht, dass ich es jemals getan habe.


95
2018-01-17 17:47



In C # 5.0 ist dieses Problem behoben und Sie können Überschleifenvariablen schließen und die erwarteten Ergebnisse erhalten.

Die Sprachspezifikation sagt:

8.8.4 Die foreach-Aussage

(...)

Eine foreach Aussage des Formulars

foreach (V v in x) embedded-statement

wird dann erweitert auf:

{
  E e = ((C)(x)).GetEnumerator();
  try {
      while (e.MoveNext()) {
          V v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}

(...)

Die Platzierung von v Innerhalb der While-Schleife ist wichtig, wie es ist   erfasst durch eine anonyme Funktion, die in der   eingebettete Anweisung. Beispielsweise:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();

Ob v außerhalb der While-Schleife deklariert wurde, würde es geteilt werden   unter allen Iterationen, und sein Wert nach der for-Schleife wäre die   Endwert, 13, was die Invokation von ist f würde drucken.   Stattdessen, weil jede Iteration ihre eigene Variable hat v, der Eine   gefangen von f in der ersten Iteration wird weiterhin den Wert halten    7, was gedruckt wird. (Hinweis: frühere Versionen von C #   erklärt v außerhalb der While-Schleife.)


52
2017-09-03 13:58



Meiner Meinung nach ist das eine merkwürdige Frage. Es ist gut zu wissen, wie der Compiler funktioniert, aber das ist nur "gut zu wissen".

Wenn Sie Code schreiben, der vom Algorithmus des Compilers abhängt, ist es eine schlechte Übung. Und es ist besser, Code neu zu schreiben, um diese Abhängigkeit auszuschließen.

Das ist eine gute Frage für ein Vorstellungsgespräch. Aber im wirklichen Leben habe ich keine Probleme, die ich im Bewerbungsgespräch gelöst habe.

Die 90% von foreach verwendet jedes Element der Sammlung (nicht um einige Werte auszuwählen oder zu berechnen). Manchmal müssen Sie einige Werte innerhalb der Schleife berechnen, aber es ist nicht gut, eine BIG-Schleife zu erstellen.

Es ist besser, LINQ-Ausdrücke zum Berechnen der Werte zu verwenden. Weil, wenn Sie eine Menge Sache innerhalb der Schleife, nach 2-3 Monaten berechnen, wenn Sie (oder jemand anderes) diesen Code lesen wird, wird Person nicht verstehen, was das ist und wie es funktionieren soll.


0
2018-06-15 07:31