Jeśli używam:
var strings = new List<string> { "sample" };
foreach (string s in strings)
{
Console.WriteLine(s);
strings.Add(s + "!");
}
Add
w foreach
rzuca InvalidOperationException (Kolekcja została zmodyfikowana; operacja wyliczania nie może wykonać), które uważam za logiczne, ponieważ jesteśmy pociągając dywanik spod naszych stóp.
Jeśli jednak używam:
var strings = new List<string> { "sample" };
strings.ForEach(s =>
{
Console.WriteLine(s);
strings.Add(s + "!");
});
natychmiast strzela sobie w stopę, wykonując pętlę, aż wyrzuci wyjątek OutOfMemoryException.
Zaskoczyło mnie to, ponieważ zawsze myślałem, że List.ForEach to tylko opakowanie dla foreach
lub dla for
.
Czy ktoś ma wyjaśnienie, jak i dlaczego to zachowanie?
(Zainspirowany pętlą ForEach dla listy ogólnej powtarzanej w nieskończoność )
foreach
lub dlafor
”. Nadal przydałby sięfor
. Możesz wykonać tę samą akcję wfor
pętli i wygenerować ten sam wyjątek OutOfMemoryException.Odpowiedzi:
Dzieje się tak, ponieważ
ForEach
metoda nie używa modułu wyliczającego, przechodzi przez elementy zfor
pętlą:public void ForEach(Action<T> action) { if (action == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); } for (int i = 0; i < this._size; i++) { action(this._items[i]); } }
(kod uzyskany z JustDecompile)
Ponieważ moduł wyliczający nie jest używany, nigdy nie sprawdza, czy lista uległa zmianie, a warunek końcowy
for
pętli nigdy nie zostanie osiągnięty, ponieważ_size
jest zwiększany przy każdej iteracji.źródło
_size
obliczane? Jeśli jest to tylko wstępnie obliczone, to należy uruchomić raz dla mojego przykładu. To oczywiście jakoś odświeżone._version
zmienna prywatna,List<T>
która może wykrywać tego rodzaju scenariusze, ponieważ jest aktualizowana na podstawie operacji, które zmieniają samą listę.List<T>.ForEach
jest zaimplementowanyfor
wewnątrz, dzięki czemu nie korzysta z modułu wyliczającego i pozwala na modyfikację kolekcji.źródło
Ponieważ ForEach dołączona do klasy List wewnętrznie używa pętli for, która jest bezpośrednio dołączona do jej wewnętrznych elementów członkowskich - co można zobaczyć, pobierając kod źródłowy platformy .NET Framework.
http://referencesource.microsoft.com/netframework.aspx
Gdzie jako pętla foreach jest przede wszystkim optymalizacją kompilatora, ale także musi działać na kolekcji jako obserwator - więc jeśli kolekcja zostanie zmodyfikowana, zgłasza wyjątek.
źródło
Add
wierszstrings.Insert(0, s + "!")
po prostu wypisuje „próbkę”. Dziwne, że nie ma o tym w ogóle wzmianki w dokumentacji.Wiemy o tym problemie, było to przeoczenie, kiedy zostało pierwotnie napisane. Niestety nie możemy tego zmienić, ponieważ uniemożliwiłoby to teraz uruchomienie tego wcześniej działającego kodu:
var list = new List<string>(); list.Add("Foo"); list.Add("Bar"); list.ForEach((item) => { if(item=="Foo") list.Remove(item); });
Sama użyteczność tej metody jest wątpliwa, jak zauważył Eric Lippert , więc nie uwzględniliśmy jej dla .NET dla aplikacji w stylu Metro (tj. Aplikacji Windows 8).
David Kean (zespół BCL)
źródło