Wenn ich benutze:
var strings = new List<string> { "sample" };
foreach (string s in strings)
{
Console.WriteLine(s);
strings.Add(s + "!");
}
Das Add
in den foreach
Würfen eine InvalidOperationException (Sammlung wurde geändert; Aufzählungsoperation wird möglicherweise nicht ausgeführt), die ich für logisch halte, da wir den Teppich unter unseren Füßen hervorziehen.
Wenn ich jedoch benutze:
var strings = new List<string> { "sample" };
strings.ForEach(s =>
{
Console.WriteLine(s);
strings.Add(s + "!");
});
Es schießt sich sofort in den Fuß, indem es eine Schleife ausführt, bis eine OutOfMemoryException ausgelöst wird.
Dies ist eine Überraschung für mich, da ich immer dachte, dass List.ForEach entweder nur ein Wrapper für foreach
oder für ist for
.
Hat jemand eine Erklärung für das Wie und Warum dieses Verhaltens?
(Inspiriert von der ForEach-Schleife für eine endlos wiederholte generische Liste )
foreach
oder für istfor
." Es könnte noch gebrauchenfor
. Sie können dieselbe Aktion in einerfor
Schleife ausführen und als Ergebnis dieselbe OutOfMemoryException generieren.Antworten:
Dies liegt daran, dass die
ForEach
Methode den Enumerator nicht verwendet, sondern die Elemente mit einerfor
Schleife durchläuft :(Code mit JustDecompile erhalten)
Da der Enumerator nicht verwendet wird, wird nie überprüft, ob sich die Liste geändert hat, und die Endbedingung der
for
Schleife wird nie erreicht, da sie_size
bei jeder Iteration erhöht wird.quelle
_size
berechnet? Wenn es nur vorberechnet ist, sollte es für mein Beispiel nur einmal ausgeführt werden. Es ist offensichtlich irgendwie erfrischt._version
private VariableList<T>
, die diese Art von Szenarien erkennen kann, da sie bei Vorgängen aktualisiert wird, die die Liste selbst ändern.List<T>.ForEach
wird überfor
inside implementiert , verwendet also keinen Enumerator und ermöglicht das Ändern der Sammlung.quelle
Da das an die List-Klasse angehängte ForEach intern eine for-Schleife verwendet, die direkt an die internen Mitglieder angehängt ist. Dies können Sie sehen, indem Sie den Quellcode für das .NET-Framework herunterladen.
http://referencesource.microsoft.com/netframework.aspx
Wobei eine foreach-Schleife in erster Linie eine Compileroptimierung ist, aber auch als Beobachter gegen die Sammlung arbeiten muss. Wenn also die Sammlung geändert wird, wird eine Ausnahme ausgelöst.
quelle
Add
Zeile ändern , wirdstrings.Insert(0, s + "!")
nur "Probe" ausgedruckt. Es ist seltsam, dass dies in der Dokumentation überhaupt nicht erwähnt wird.Wir wissen über dieses Problem Bescheid, es war ein Versehen, als es ursprünglich geschrieben wurde. Leider können wir es nicht ändern, da dies jetzt verhindern würde, dass dieser zuvor funktionierende Code ausgeführt wird:
Die Nützlichkeit dieser Methode selbst ist fraglich, wie Eric Lippert betonte, weshalb wir sie für .NET für Apps im Metro-Stil (dh Windows 8-Apps) nicht aufgenommen haben.
David Kean (BCL-Team)
quelle