Es gibt anscheinend viele Möglichkeiten, eine Sammlung zu durchlaufen. Neugierig, ob es Unterschiede gibt oder warum Sie einen Weg über den anderen verwenden würden.
Erster Typ:
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
Andere Weise:
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
Ich nehme an, dass Sie anstelle des oben verwendeten anonymen Delegaten einen wiederverwendbaren Delegierten haben würden, den Sie angeben könnten ...
Antworten:
Es gibt eine wichtige und nützliche Unterscheidung zwischen den beiden.
Da .ForEach eine
for
Schleife verwendet, um die Sammlung zu iterieren, ist dies gültig (edit: vor .net 4.5 - die Implementierung wurde geändert und beide werfen):Während
foreach
ein Enumerator verwendet wird, ist dies nicht gültig:tl; dr: Kopieren Sie diesen Code NICHT in Ihre Anwendung!
Diese Beispiele sind keine bewährten Methoden, sondern dienen lediglich der Veranschaulichung der Unterschiede zwischen
ForEach()
undforeach
.Das Entfernen von Elementen aus einer Liste innerhalb einer
for
Schleife kann Nebenwirkungen haben. Die häufigste ist in den Kommentaren zu dieser Frage beschrieben.Wenn Sie mehrere Elemente aus einer Liste entfernen möchten, möchten Sie im Allgemeinen die Bestimmung der zu entfernenden Elemente von der tatsächlichen Entfernung trennen. Es hält Ihren Code nicht kompakt, garantiert aber, dass Sie keine Elemente verpassen.
quelle
Wir hatten hier Code (in VS2005 und C # 2.0), bei dem die vorherigen Ingenieure alles daran gesetzt haben,
list.ForEach( delegate(item) { foo;});
anstelle desforeach(item in list) {foo; };
gesamten von ihnen geschriebenen Codes zu verwenden. zB ein Codeblock zum Lesen von Zeilen aus einem dataReader.Ich weiß immer noch nicht genau, warum sie das getan haben.
Die Nachteile von
list.ForEach()
sind:In C # 2.0 ist es ausführlicher. Ab C # 3 können Sie jedoch die
=>
Syntax " " verwenden, um einige schöne, knappe Ausdrücke zu erstellen.Es ist weniger bekannt. Leute, die diesen Code pflegen müssen, werden sich fragen, warum Sie das so gemacht haben. Ich brauchte eine Weile, um zu entscheiden, dass es keinen Grund gab, außer vielleicht, um den Autor klug erscheinen zu lassen (die Qualität des restlichen Codes untergrub dies). Es war auch weniger lesbar, mit dem "
})
" am Ende des Delegatencodeblocks.Siehe auch Bill Wagners Buch "Effektives C #: 50 spezifische Möglichkeiten zur Verbesserung Ihres C #", in dem er darüber spricht, warum foreach anderen Schleifen wie for- oder while-Schleifen vorgezogen wird - der Hauptpunkt ist, dass Sie den Compiler entscheiden lassen, wie am besten konstruiert werden soll die Schleife. Wenn es einer zukünftigen Version des Compilers gelingt, eine schnellere Technik zu verwenden, erhalten Sie diese kostenlos, indem Sie foreach verwenden und neu erstellen, anstatt Ihren Code zu ändern.
ein
foreach(item in list)
Konstrukt , können Sie verwendenbreak
odercontinue
wenn Sie die Iteration oder die Schleife beenden müssen. Sie können die Liste jedoch nicht in einer foreach-Schleife ändern.Ich bin überrascht zu sehen, dass
list.ForEach
das etwas schneller ist. Aber das ist wahrscheinlich kein triftiger Grund, es durchgehend zu verwenden, das wäre eine vorzeitige Optimierung. Wenn Ihre Anwendung eine Datenbank oder einen Webdienst verwendet, bei dem es sich nicht um eine Schleifensteuerung handelt, ist dies fast immer der Ort, an dem die Zeit vergeht. Und haben Sie es auch mit einerfor
Schleife verglichen? Daslist.ForEach
könnte schneller sein, weil es intern verwendet wird, und einefor
Schleife ohne den Wrapper wäre noch schneller.Ich bin nicht der Meinung, dass die
list.ForEach(delegate)
Version in irgendeiner Weise "funktionaler" ist. Es wird zwar eine Funktion an eine Funktion übergeben, es gibt jedoch keinen großen Unterschied im Ergebnis oder in der Programmorganisation.Ich glaube nicht, dass
foreach(item in list)
"genau sagt, wie Sie es wollen" - einefor(int 1 = 0; i < count; i++)
Schleife macht das, eineforeach
Schleife überlässt die Wahl der Steuerung dem Compiler.Ich habe das Gefühl, bei einem neuen Projekt
foreach(item in list)
für die meisten Schleifen zu verwenden, um die allgemeine Verwendung und Lesbarkeit einzuhalten, undlist.Foreach()
nur für kurze Blöcke zu verwenden, wenn Sie mit dem C # 3 "=>
" -Operator etwas eleganteres oder kompakteres tun können . In solchen Fällen gibt es möglicherweise bereits eine LINQ-Erweiterungsmethode, die spezifischer ist alsForEach()
. Sehen Sie, wennWhere()
,Select()
,Any()
,All()
,Max()
oder eine der vielen anderen LINQ Methoden nicht bereits tun , was Sie aus der Schleife möchten.quelle
Zum Spaß habe ich List in den Reflektor gesteckt und dies ist das resultierende C #:
In ähnlicher Weise ist der MoveNext im Enumerator, der von foreach verwendet wird, folgender:
Die List.ForEach ist viel kürzer als MoveNext - weit weniger Verarbeitung - wird JIT eher zu etwas Effizientem machen.
Außerdem weist foreach () unabhängig davon einen neuen Enumerator zu. Der GC ist Ihr Freund, aber wenn Sie wiederholt dasselbe tun, werden mehr wegwerfbare Objekte erzeugt, anstatt denselben Delegierten wiederzuverwenden - ABER - dies ist wirklich ein Randfall. Im typischen Gebrauch werden Sie wenig oder keinen Unterschied sehen.
quelle
Ich kenne zwei obskure Dinge, die sie anders machen. Geh mich!
Erstens gibt es den klassischen Fehler, für jedes Element in der Liste einen Delegaten zu erstellen. Wenn Sie das Schlüsselwort foreach verwenden, verweisen alle Ihre Delegierten möglicherweise auf das letzte Element der Liste:
Die List.ForEach-Methode hat dieses Problem nicht. Das aktuelle Element der Iteration wird als Argument an das äußere Lambda übergeben, und das innere Lambda erfasst dieses Argument dann korrekt in seinem eigenen Abschluss. Problem gelöst.
(Leider glaube ich, dass ForEach eher ein Mitglied von List als eine Erweiterungsmethode ist, obwohl es einfach ist, es selbst zu definieren, sodass Sie diese Funktion für jeden aufzählbaren Typ haben.)
Zweitens weist der ForEach-Methodenansatz eine Einschränkung auf. Wenn Sie IEnumerable mithilfe von Yield Return implementieren, können Sie innerhalb des Lambda keine Yield Return durchführen. Ein Durchlaufen der Elemente in einer Sammlung, um Rückgaben zu erzielen, ist mit dieser Methode nicht möglich. Sie müssen das Schlüsselwort foreach verwenden und das Schließproblem umgehen, indem Sie manuell eine Kopie des aktuellen Schleifenwerts innerhalb der Schleife erstellen.
Mehr hier
quelle
foreach
ist in C # 5 "behoben". Stackoverflow.com/questions/8898925/…Ich denke, der
someList.ForEach()
Anruf könnte leicht parallelisiert werden, während das normaleforeach
nicht so einfach parallel zu laufen ist. Sie können problemlos mehrere verschiedene Delegaten auf verschiedenen Kernen ausführen, was mit einem normalen System nicht so einfach istforeach
.Nur meine 2 Cent
quelle
Wie sie sagen, der Teufel steckt im Detail ...
Der größte Unterschied zwischen den beiden Methoden der Aufzählung von Sammlungen besteht darin, dass
foreach
der Status übertragen wird, währendForEach(x => { })
dies nicht der Fall ist.Aber lassen Sie uns etwas tiefer gehen, denn es gibt einige Dinge, die Sie beachten sollten, die Ihre Entscheidung beeinflussen können, und es gibt einige Einschränkungen, die Sie beim Codieren für beide Fälle beachten sollten.
Verwenden
List<T>
wir in unserem kleinen Experiment das Verhalten. Für dieses Experiment verwende ich .NET 4.7.2:Lassen Sie uns dies
foreach
zuerst wiederholen :Wir könnten dies erweitern in:
Mit dem Enumerator in der Hand sehen wir unter die Decke:
Zwei Dinge werden sofort offensichtlich:
Dies ist natürlich in keiner Weise threadsicher. Wie oben erwähnt, ist das Ändern der Sammlung während des Iterierens nur ein schlechtes Mojo.
Aber was ist mit dem Problem, dass die Sammlung während der Iteration ungültig wird, wenn wir nicht mit der Sammlung während der Iteration herumspielen? Best Practices empfehlen, die Sammlung während des Betriebs und der Iteration zu versionieren und Versionen zu überprüfen, um festzustellen, wann sich die zugrunde liegende Sammlung ändert.
Hier wird es richtig trübe. Laut Microsoft-Dokumentation:
Was bedeutet das? Nur weil
List<T>
die Ausnahmebehandlung implementiertIList<T>
wird, bedeutet dies nicht, dass alle Sammlungen, die implementiert werden, dasselbe tun. Dies scheint ein klarer Verstoß gegen das Liskov-Substitutionsprinzip zu sein:Ein weiteres Problem besteht darin, dass der Enumerator implementiert werden muss. Dies
IDisposable
bedeutet, dass eine andere Quelle potenzieller Speicherlecks auftritt, nicht nur, wenn der Aufrufer etwas falsch macht, sondern wenn der Autor dasDispose
Muster nicht korrekt implementiert .Schließlich haben wir ein lebenslanges Problem ... Was passiert, wenn der Iterator gültig ist, die zugrunde liegende Sammlung jedoch nicht mehr vorhanden ist? Wir haben jetzt eine Momentaufnahme dessen, was war ... Wenn Sie die Lebensdauer einer Sammlung von ihren Iteratoren trennen, fragen Sie nach Problemen.
Untersuchen wir nun
ForEach(x => { })
:Dies erweitert sich auf:
Von wichtiger Bedeutung ist Folgendes:
for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);
Dieser Code weist keine Enumeratoren zu (nichts zu
Dispose
) und pausiert nicht während der Iteration.Beachten Sie, dass hierdurch auch eine flache Kopie der zugrunde liegenden Sammlung erstellt wird, die Sammlung jedoch jetzt eine Momentaufnahme ist. Wenn der Autor eine Überprüfung, ob sich die Sammlung ändert oder veraltet ist, nicht korrekt implementiert, ist der Schnappschuss weiterhin gültig.
Dies schützt Sie in keiner Weise vor dem Problem der lebenslangen Probleme. Wenn die zugrunde liegende Sammlung verschwindet, haben Sie jetzt eine flache Kopie, die auf das verweist, was war. Aber zumindest haben Sie kein
Dispose
Problem damit beschäftigen sich mit verwaisten Iteratoren ...Ja, ich sagte Iteratoren ... manchmal ist es vorteilhaft, einen Zustand zu haben. Angenommen, Sie möchten etwas beibehalten, das einem Datenbankcursor ähnelt. Vielleicht sind mehrere
foreach
StileIterator<T>
der richtige Weg. Ich persönlich mag diesen Designstil nicht, da es zu viele Probleme auf Lebenszeit gibt und Sie sich auf die guten Seiten der Autoren der Sammlungen verlassen, auf die Sie sich verlassen (es sei denn, Sie schreiben buchstäblich alles selbst von Grund auf neu).Es gibt immer eine dritte Option ...
Es ist nicht sexy, aber es hat Zähne (Entschuldigung an Tom Cruise und den Film The Firm )
Es ist Ihre Wahl, aber jetzt wissen Sie es und es kann eine informierte sein.
quelle
Sie könnten den anonymen Delegierten benennen :-)
Und Sie können die zweite schreiben als:
Was ich bevorzuge und viel Tipparbeit erspare.
Wie Joachim sagt, lässt sich Parallelität leichter auf die zweite Form anwenden.
quelle
Hinter den Kulissen wird der anonyme Delegat zu einer tatsächlichen Methode, sodass Sie bei der zweiten Auswahl einen gewissen Aufwand haben können, wenn der Compiler die Funktion nicht inline verwendet. Darüber hinaus würden sich alle lokalen Variablen, auf die der Hauptteil des anonymen Delegatenbeispiels verweist, aufgrund von Compilertricks ändern, um die Tatsache zu verbergen, dass sie zu einer neuen Methode kompiliert werden. Weitere Informationen dazu, wie C # diese Magie ausübt:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
quelle
Die ForEach-Funktion ist Mitglied der generischen Klassenliste.
Ich habe die folgende Erweiterung erstellt, um den internen Code zu reproduzieren:
Am Ende verwenden wir also ein normales foreach (oder eine Schleife, wenn Sie möchten).
Auf der anderen Seite ist die Verwendung einer Delegatenfunktion nur eine andere Möglichkeit, eine Funktion zu definieren. Dieser Code:
ist äquivalent zu:
oder mit Labda-Ausdrücken:
quelle
Der gesamte ForEach-Bereich (Delegate-Funktion) wird als einzelne Codezeile behandelt (Aufruf der Funktion), und Sie können keine Haltepunkte festlegen oder in den Code eintreten. Wenn eine nicht behandelte Ausnahme auftritt, wird der gesamte Block markiert.
quelle
List.ForEach () wird als funktionaler angesehen.
List.ForEach()
sagt, was du tun willst.foreach(item in list)
sagt auch genau, wie du es machen willst. Dies lässt dieList.ForEach
Möglichkeit, die Implementierung des Wie- Teils in der Zukunft zu ändern . Beispielsweise kann eine hypothetische zukünftige Version von .Net immerList.ForEach
parallel ausgeführt werden, unter der Annahme, dass zu diesem Zeitpunkt jeder über eine Reihe von CPU-Kernen verfügt, die im Allgemeinen im Leerlauf sitzen.Auf der anderen Seite haben
foreach (item in list)
Sie etwas mehr Kontrolle über die Schleife. Sie wissen beispielsweise, dass die Elemente in einer sequentiellen Reihenfolge iteriert werden, und Sie können leicht in der Mitte brechen, wenn ein Element eine bestimmte Bedingung erfüllt.Einige neuere Anmerkungen zu diesem Thema finden Sie hier:
quelle
Die zweite Möglichkeit, die Sie gezeigt haben, verwendet eine Erweiterungsmethode, um die Delegatmethode für jedes der Elemente in der Liste auszuführen.
Auf diese Weise haben Sie einen weiteren Delegatenaufruf (= Methode).
Zusätzlich besteht die Möglichkeit, die Liste mit einer for- Schleife zu iterieren .
quelle
Eine Sache, vor der Sie vorsichtig sein sollten, ist das Beenden der generischen .ForEach-Methode - siehe diese Diskussion . Obwohl der Link zu sagen scheint, dass dieser Weg der schnellste ist. Ich weiß nicht warum - Sie würden denken, dass sie nach der Kompilierung gleichwertig wären ...
quelle
Es gibt einen Weg, den ich in meiner App gemacht habe:
Sie können das als Ihren Artikel aus dem Foreach verwenden
quelle