Diese Warnung besteht aus zwei Teilen. Das erste ist...
Zugriff auf jede Variable im Abschluss
... was an sich nicht ungültig ist, aber auf den ersten Blick kontraintuitiv. Es ist auch sehr schwer, es richtig zu machen. (So sehr, dass der Artikel, auf den ich unten verweise, dies als "schädlich" beschreibt.)
Nehmen Sie Ihre Anfrage entgegen und stellen Sie fest, dass der Code, den Sie extrahiert haben, im Grunde eine erweiterte Form dessen ist, was der C # -Compiler (vor C # 5) für foreach1 generiert :
Ich verstehe nicht, warum Folgendes nicht gültig ist:
string s; while (enumerator.MoveNext()) { s = enumerator.Current; ...
Nun, es ist syntaktisch gültig. Und wenn Sie in Ihrer Schleife nur den Wert von verwenden, sist alles gut. Das Schließen sführt jedoch zu einem kontraintuitiven Verhalten. Schauen Sie sich den folgenden Code an:
var countingActions = new List<Action>();
var numbers = from n in Enumerable.Range(1, 5)
select n.ToString(CultureInfo.InvariantCulture);
using (var enumerator = numbers.GetEnumerator())
{
string s;
while (enumerator.MoveNext())
{
s = enumerator.Current;
Console.WriteLine("Creating an action where s == {0}", s);
Action action = () => Console.WriteLine("s == {0}", s);
countingActions.Add(action);
}
}
Wenn Sie diesen Code ausführen, erhalten Sie die folgende Konsolenausgabe:
Creating an action where s == 1
Creating an action where s == 2
Creating an action where s == 3
Creating an action where s == 4
Creating an action where s == 5
Das erwarten Sie.
Führen Sie den folgenden Code unmittelbar nach dem obigen Code aus, um etwas zu sehen, das Sie wahrscheinlich nicht erwarten :
foreach (var action in countingActions)
action();
Sie erhalten die folgende Konsolenausgabe:
s == 5
s == 5
s == 5
s == 5
s == 5
Warum? Weil wir fünf Funktionen erstellt haben, die alle genau dasselbe tun: Drucken Sie den Wert von s(den wir geschlossen haben). In Wirklichkeit haben sie dieselbe Funktion ("Drucken s", "Drucken s", "Drucken s" ...).
An dem Punkt, an dem wir sie verwenden, tun sie genau das, was wir verlangen: Drucken Sie den Wert von aus s. Wenn Sie sich den letzten bekannten Wert von ansehen s, werden Sie sehen, dass dies der Fall ist 5. Also werden wir s == 5fünfmal auf die Konsole gedruckt.
Welches ist genau das, wonach wir gefragt haben, aber wahrscheinlich nicht das, was wir wollen.
Der zweite Teil der Warnung ...
Kann sich beim Kompilieren mit verschiedenen Compilerversionen unterschiedlich verhalten.
...es ist was es ist. Beginnend mit C # 5 generiert der Compiler einen anderen Code, der dies "verhindert"foreach .
Daher führt der folgende Code unter verschiedenen Versionen des Compilers zu unterschiedlichen Ergebnissen:
foreach (var n in numbers)
{
Action action = () => Console.WriteLine("n == {0}", n);
countingActions.Add(action);
}
Folglich wird auch die R # -Warnung ausgegeben :)
Mein erstes Code-Snippet oben zeigt in allen Versionen des Compilers das gleiche Verhalten, da ich es nicht verwende foreach(vielmehr habe ich es so erweitert, wie es Compiler vor C # 5 tun).
Ist das für die CLR-Version?
Ich bin mir nicht ganz sicher, was Sie hier fragen.
Eric Lipperts Beitrag besagt, dass die Änderung "in C # 5" erfolgt. SoVermutlich müssen Sie auf .NET 4.5 oder höher abzielen mit einem C # 5 oder höher Compiler, um das neue Verhalten zu erhalten, und alles davor bekommt das alte Verhalten.
Es ist jedoch eine Funktion des Compilers und nicht der .NET Framework-Version.
Gibt es Relevanz für IL?
Unterschiedlicher Code erzeugt unterschiedliche IL, sodass in diesem Sinne Konsequenzen für die generierte IL entstehen.
1 foreach ist ein viel häufigeres Konstrukt als der Code, den Sie in Ihrem Kommentar gepostet haben. Das Problem tritt normalerweise durch die Verwendung von foreachund nicht durch manuelle Aufzählung auf. Aus diesem Grund verhindern die Änderungen foreachin C # 5 dieses Problem, jedoch nicht vollständig.