Najwyraźniej istnieje wiele sposobów na iterację kolekcji. Ciekawe, czy są jakieś różnice lub dlaczego używałbyś jednej metody zamiast drugiej.
Pierwszy typ:
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
Inny sposób:
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
Przypuszczam, że zamiast anonimowego delegata, którego używam powyżej, miałbyś delegata wielokrotnego użytku, którego możesz określić ...
Odpowiedzi:
Jest jedna ważna i użyteczna różnica między nimi.
Ponieważ .ForEach używa
for
pętli do iteracji kolekcji, jest to poprawne (edit: przed .net 4.5 - implementacja się zmieniła i oba rzucają):podczas gdy
foreach
używa modułu wyliczającego, więc to nie jest poprawne:tl; dr: NIE kopiuj tego kodu do swojej aplikacji!
Te przykłady nie są najlepszymi praktykami, służą jedynie do pokazania różnic między
ForEach()
iforeach
.Usuwanie elementów z listy w
for
pętli może mieć skutki uboczne. Najczęstszy jest opisany w komentarzach do tego pytania.Ogólnie rzecz biorąc, jeśli chcesz usunąć wiele elementów z listy, chciałbyś oddzielić określenie, które elementy usunąć, od faktycznego usunięcia. Nie zachowuje kompaktowości kodu, ale gwarantuje, że nie przegapisz żadnych elementów.
źródło
Mieliśmy tutaj kod (w VS2005 i C # 2.0), w którym poprzedni inżynierowie robili wszystko, aby użyć
list.ForEach( delegate(item) { foo;});
zamiastforeach(item in list) {foo; };
całego kodu, który napisali. np. blok kodu do odczytu wierszy z dataReadera.Nadal nie wiem dokładnie, dlaczego to zrobili.
Wady
list.ForEach()
to:W języku C # 2.0 jest to bardziej szczegółowe. Jednak w języku C # 3 i nowszych można użyć
=>
składni „ ”, aby utworzyć ładnie zwięzłe wyrażenia.To jest mniej znane. Ludzie, którzy muszą utrzymywać ten kod, będą się zastanawiać, dlaczego zrobiłeś to w ten sposób. Zajęło mi trochę czasu, zanim zdecydowałem, że nie ma żadnego powodu, może poza tym, że pisarz wydaje się sprytny (jakość reszty kodu podważyła to). Było też mniej czytelne, z opcją „
})
” na końcu bloku kodu delegata.Zobacz także książkę Billa Wagnera „Effective C #: 50 Specific Ways to Improve Your C #”, w której mówi o tym, dlaczego foreach jest preferowane od innych pętli, takich jak pętle for lub while - głównym punktem jest to, że pozwalasz kompilatorowi wybrać najlepszy sposób konstruowania pętla. Jeśli w przyszłej wersji kompilatora uda się zastosować szybszą technikę, otrzymasz ją za darmo, używając foreach i przebudowy zamiast zmiany kodu.
foreach(item in list)
konstrukcja pozwala na wykorzystaniebreak
lubcontinue
jeśli trzeba zamknąć lub iteracji pętli. Ale nie możesz zmieniać listy w pętli foreach.Jestem zaskoczony, że
list.ForEach
jest nieco szybszy. Ale to prawdopodobnie nie jest uzasadniony powód, aby go używać przez cały czas, to byłaby przedwczesna optymalizacja. Jeśli Twoja aplikacja korzysta z bazy danych lub usługi internetowej, która nie jest sterowana w pętli, prawie zawsze będzie tam, gdzie mija czas. Czy porównałeś to również zfor
pętlą? Pliklist.ForEach
Może być szybciej ze względu na użyciu tego wewnętrznie ifor
pętli bez owijki będzie jeszcze szybciej.Nie zgadzam się, że
list.ForEach(delegate)
wersja jest „bardziej funkcjonalna” w jakikolwiek istotny sposób. Przekazuje funkcję do funkcji, ale nie ma dużej różnicy w wyniku lub organizacji programu.Nie sądzę, że
foreach(item in list)
„mówi dokładnie, jak chcesz to zrobić” -for(int 1 = 0; i < count; i++)
pętla to robi,foreach
pętla pozostawia wybór kontroli kompilatorowi.Czuję, że w nowym projekcie będę używał
foreach(item in list)
większości pętli, aby zachować ich powszechne użycie i czytelność, i używaćlist.Foreach()
tylko dla krótkich bloków, kiedy można zrobić coś bardziej elegancko lub zwięźle za pomocą=>
operatora C # 3 " ". W takich przypadkach może już istnieć metoda rozszerzenia LINQ, która jest bardziej szczegółowa niżForEach()
. Zobacz, czyWhere()
,Select()
,Any()
,All()
,Max()
lub jedną z wielu innych metod LINQ już nie to, co chcesz zrobić z pętli.źródło
Dla zabawy wrzuciłem List do reflektora i oto wynikowy C #:
Podobnie MoveNext w module wyliczającym, który jest używany przez foreach, to:
List.ForEach jest znacznie mniejszy niż MoveNext - znacznie mniej przetwarzania - będzie bardziej prawdopodobne, że JIT stanie się czymś wydajnym.
Ponadto foreach () przydzieli nowy moduł wyliczający bez względu na wszystko. GC jest twoim przyjacielem, ale jeśli robisz to samo wielokrotnie, spowoduje to więcej wyrzuconych obiektów, w przeciwieństwie do ponownego użycia tego samego delegata - ALE - jest to naprawdę marginalna sprawa. W typowym użytkowaniu różnica jest niewielka lub żadna.
źródło
Znam dwie niejasne rzeczy, które je odróżniają. Idź do mnie!
Po pierwsze, istnieje klasyczny błąd polegający na tworzeniu delegata dla każdego elementu na liście. Jeśli użyjesz słowa kluczowego foreach, wszyscy twoi pełnomocnicy mogą w końcu odwołać się do ostatniej pozycji na liście:
Metoda List.ForEach nie ma tego problemu. Bieżący element iteracji jest przekazywany przez wartość jako argument do zewnętrznej lambdy, a następnie wewnętrzna lambda poprawnie przechwytuje ten argument w swoim własnym zamknięciu. Problem rozwiązany.
(Niestety uważam, że ForEach jest członkiem List, a nie metodą rozszerzającą, chociaż łatwo jest to zdefiniować samodzielnie, więc masz tę funkcję na dowolnym wyliczalnym typie.)
Po drugie, podejście metodą ForEach ma pewne ograniczenia. Jeśli implementujesz IEnumerable przy użyciu zwrotu zysku, nie możesz wykonać zwrotu zysku wewnątrz lambda. Tak więc przeglądanie elementów kolekcji w celu uzyskania zwracanych rzeczy nie jest możliwe przy użyciu tej metody. Będziesz musiał użyć słowa kluczowego foreach i obejść problem zamknięcia, ręcznie wykonując kopię bieżącej wartości pętli wewnątrz pętli.
Więcej tutaj
źródło
foreach
został „naprawiony” w C # 5. stackoverflow.com/questions/8898925/ ...Wydaje mi się, że
someList.ForEach()
połączenie można łatwo zrównoleglać, podczas gdy normalneforeach
nie jest łatwe do równoległego prowadzenia. Możesz łatwo uruchomić kilka różnych delegatów na różnych rdzeniach, co nie jest takie łatwe w przypadku normalnegoforeach
.Tylko moje 2 centy
źródło
Jak mówią, diabeł tkwi w szczegółach ...
Największą różnicą między tymi dwoma metodami wyliczania kolekcji jest to, że
foreach
przenosi stan, aForEach(x => { })
nie.Ale zagłębmy się trochę głębiej, ponieważ jest kilka rzeczy, o których powinieneś wiedzieć, a które mogą wpłynąć na twoją decyzję, i są pewne zastrzeżenia, o których powinieneś wiedzieć podczas kodowania dla obu przypadków.
Wykorzystajmy
List<T>
w naszym małym eksperymencie obserwację zachowania. W tym eksperymencie używam platformy .NET 4.7.2:Powtórzmy to
foreach
najpierw z :Możemy to rozszerzyć na:
Z wyliczającym w ręku zaglądając pod okładki otrzymujemy:
Dwie rzeczy stają się natychmiast oczywiste:
Nie jest to oczywiście w żaden sposób bezpieczne. Jak wskazano powyżej, zmiana kolekcji podczas iteracji jest po prostu złym mojo.
Ale co z problemem, że kolekcja staje się nieważna podczas iteracji, poprzez manipulowanie kolekcją poza nami podczas iteracji? Najlepsze rozwiązania sugerują przechowywanie wersji kolekcji podczas operacji i iteracji oraz sprawdzanie wersji w celu wykrycia zmian w kolekcji bazowej.
Tutaj sprawy stają się naprawdę mętne. Zgodnie z dokumentacją Microsoft:
Co to znaczy? Tytułem przykładu, tylko dlatego, że
List<T>
implementuje obsługę wyjątków, nie oznacza, że wszystkie kolekcje, które implementują,IList<T>
będą robić to samo. Wydaje się, że jest to wyraźne naruszenie zasady zastępowania Liskova:Innym problemem jest to, że moduł wyliczający musi zaimplementować
IDisposable
- oznacza to kolejne źródło potencjalnych wycieków pamięci, nie tylko jeśli wywołujący pomylił się, ale jeśli autor nie implementujeDispose
poprawnie wzorca.Na koniec mamy problem dożywotni ... co się stanie, jeśli iterator jest prawidłowy, ale podstawowa kolekcja zniknęła? Teraz mamy migawkę tego, co było ... kiedy oddzielasz okres istnienia kolekcji i jej iteratorów, prosisz o kłopoty.
Przyjrzyjmy się teraz
ForEach(x => { })
:To rozszerza się do:
Na uwagę zasługują następujące kwestie:
for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);
Ten kod nie przydziela żadnych modułów wyliczających (nic do
Dispose
) i nie zatrzymuje się podczas iteracji.Należy pamiętać, że powoduje to również wykonanie płytkiej kopii podstawowej kolekcji, ale kolekcja jest teraz migawką w czasie. Jeśli autor nie zaimplementuje poprawnie sprawdzenia, czy kolekcja się zmieniła lub nie stała się „przestarzała”, migawka jest nadal ważna.
To w żaden sposób nie chroni Cię przed problemem problemów na całe życie ... jeśli podstawowa kolekcja zniknie, masz teraz płytką kopię, która wskazuje na to, co było ... ale przynajmniej nie masz
Dispose
problemu z radzić sobie z osieroconymi iteratorami ...Tak, powiedziałem, że iteratory ... czasami warto mieć stan. Przypuśćmy, że chcesz zachować coś podobnego do kursora bazy danych ... może być najlepszym rozwiązaniem w przypadku wielu
foreach
stylówIterator<T>
. Osobiście nie podoba mi się ten styl projektowania, ponieważ jest zbyt wiele problemów życiowych, a ty polegasz na łaskach autorów kolekcji, na których polegasz (chyba że dosłownie piszesz wszystko sam od zera).Zawsze jest trzecia opcja ...
To nie jest seksowne, ale ma zęby (przepraszam Toma Cruise'a i film The Firm )
To twój wybór, ale teraz już wiesz i może być świadomy.
źródło
Możesz nazwać anonimowego delegata :-)
A drugie możesz napisać jako:
Które wolę i oszczędza dużo pisania.
Jak mówi Joachim, równoległość łatwiej zastosować do drugiej formy.
źródło
W tle anonimowy delegat zostaje zamieniony w rzeczywistą metodę, więc możesz mieć trochę narzutu z drugim wyborem, jeśli kompilator nie zdecydował się na wbudowanie funkcji. Ponadto wszelkie zmienne lokalne, do których odwołuje się treść przykładu anonimowego delegata, zmieniłyby swoją naturę z powodu sztuczek kompilatora, aby ukryć fakt, że jest kompilowany do nowej metody. Więcej informacji o tym, jak C # robi tę magię:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
źródło
Funkcja ForEach jest członkiem klasy ogólnej List.
Utworzyłem następujące rozszerzenie, aby odtworzyć kod wewnętrzny:
Więc na koniec używamy normalnego foreach (lub pętli, jeśli chcesz).
Z drugiej strony użycie funkcji delegata to po prostu kolejny sposób definiowania funkcji, ten kod:
jest równa:
lub używając wyrażeń labda:
źródło
Cały zakres ForEach (funkcja delegata) jest traktowany jako pojedynczy wiersz kodu (wywołanie funkcji) i nie można ustawiać punktów przerwania ani wchodzić do kodu. Jeśli wystąpi nieobsługiwany wyjątek, zaznaczany jest cały blok.
źródło
List.ForEach () jest uważana za bardziej funkcjonalną.
List.ForEach()
mówi, co chcesz zrobić.foreach(item in list)
mówi też dokładnie, jak chcesz to zrobić. To pozostawiaList.ForEach
swobodę zmiany realizację how części w przyszłości. Na przykład, hipotetyczna przyszła wersja .Net może zawsze działaćList.ForEach
równolegle, przy założeniu, że w tym momencie każdy ma pewną liczbę rdzeni procesora, które są zwykle bezczynne.Z drugiej strony
foreach (item in list)
daje trochę większą kontrolę nad pętlą. Na przykład wiesz, że elementy będą iterowane w jakiejś kolejności sekwencyjnej i możesz łatwo złamać w środku, jeśli element spełni jakiś warunek.Kilka nowszych uwag na ten temat jest dostępnych tutaj:
źródło
Drugi sposób, który pokazałeś, używa metody rozszerzającej do wykonania metody delegata dla każdego elementu na liście.
W ten sposób masz kolejne wywołanie delegata (= metody).
Dodatkowo istnieje możliwość iteracji listy za pomocą pętli for .
źródło
Należy uważać na to, jak wyjść z metody Generic .ForEach - zobacz tę dyskusję . Chociaż link wydaje się mówić, że ta droga jest najszybsza. Nie wiem dlaczego - można by pomyśleć, że byłyby równoważne po skompilowaniu ...
źródło
Jest sposób, zrobiłem to w mojej aplikacji:
Możesz użyć jako przedmiotu z każdego
źródło