it-swarm.com.de

foreach vs someList.ForEach () {}

Es gibt anscheinend viele Möglichkeiten, eine Sammlung zu durchlaufen. Neugierig, ob es Unterschiede gibt oder warum Sie einen Weg über den anderen wählen.

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 genannten anonymen Delegaten einen wiederverwendbaren Delegaten haben, den Sie angeben können ...

143
Bryce Fischer

Es gibt einen wichtigen und nützlichen Unterschied zwischen den beiden.

Da .ForEach eine for -Schleife verwendet, um die Auflistung zu durchlaufen, ist dies gültig (edit: vor .net 4.5 - die Implementierung wurde geändert und beide werfen):

someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); }); 

während foreach einen Enumerator verwendet, ist dies nicht gültig:

foreach(var item in someList)
  if(item.RemoveMe) someList.Remove(item);

tl; dr: Kopieren Sie diesen Code NICHT in Ihre Anwendung!

Diese Beispiele sind keine bewährten Methoden, sondern dienen lediglich zur Veranschaulichung der Unterschiede zwischen ForEach() und foreach.

Das Entfernen von Elementen aus einer Liste in 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, aber es garantiert, dass Sie keine Elemente verpassen.

121
Will

Wir hatten hier Code (in VS2005 und C # 2.0), in dem die vorherigen Ingenieure sich Mühe gaben, list.ForEach( delegate(item) { foo;}); anstelle von foreach(item in list) {foo; }; für den gesamten von ihnen geschriebenen Code zu verwenden. z.B. 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 sehr knappe Ausdrücke zu erstellen.

  • Es ist weniger vertraut. Leute, die diesen Code pflegen müssen, werden sich fragen, warum Sie das so gemacht haben. Es dauerte eine Weile, bis ich herausfand, dass es keinen Grund gab, außer vielleicht den Schreiber schlau erscheinen zu lassen (die Qualität des restlichen Codes untergrub das). Es war auch weniger lesbar, mit dem "})" Am Ende des Delegate-Codeblocks.

  • Siehe auch Bill Wagners Buch "Effective C #: 50 Specific Ways to Improve Your C #", in dem er erklärt, warum foreach anderen Schleifen wie for- oder while-Schleifen vorgezogen wird die Schleife. Wenn eine zukünftige Version des Compilers eine schnellere Technik verwendet, erhalten Sie diese kostenlos, indem Sie foreach verwenden und neu erstellen, anstatt Ihren Code zu ändern.

  • mit einem foreach(item in list) -Konstrukt können Sie break oder continue verwenden, wenn Sie die Iteration oder die Schleife verlassen müssen. Sie können die Liste in einer foreach-Schleife jedoch nicht ändern.

Ich bin überrascht zu sehen, dass list.ForEach 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, die bzw. der fast immer dort ist, wo die Zeit vergeht, und nicht die Schleifensteuerung. Und haben Sie es auch mit einer for Schleife verglichen? Das list.ForEach Könnte schneller sein, da es intern verwendet wird, und eine for -Schleife ohne den Wrapper wäre sogar noch schneller.

Ich bin nicht einverstanden, 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 in Bezug auf das Ergebnis oder die Programmorganisation.

Ich glaube nicht, dass foreach(item in list) "genau sagt, wie Sie es wollen" - eine for(int 1 = 0; i < count; i++) -Schleife macht das, eine foreach -Schleife überlässt die Wahl der Steuerung der Compiler.

Ich habe das Gefühl, in einem neuen Projekt foreach(item in list) für die meisten Schleifen zu verwenden, um den allgemeinen Gebrauch und die Lesbarkeit zu gewährleisten, und list.Foreach() nur für kurze Blöcke zu verwenden, wenn Sie können Mit dem C # 3-Operator "=>" können Sie eleganter oder kompakter vorgehen. In solchen Fällen gibt es möglicherweise bereits eine LINQ-Erweiterungsmethode, die spezifischer ist als ForEach(). Prüfen Sie, ob Where(), Select(), Any(), All(), Max() oder eine der vielen anderen LINQ-Methoden dies nicht tut mach schon was du willst aus der Schleife.

73
Anthony

Zum Spaß tauchte ich List in Reflektor und dies ist die resultierende C #:

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]);
    }
}

In ähnlicher Weise lautet der MoveNext im Enumerator, der von foreach verwendet wird, wie folgt:

public bool MoveNext()
{
    if (this.version != this.list._version)
    {
        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
    }
    if (this.index < this.list._size)
    {
        this.current = this.list._items[this.index];
        this.index++;
        return true;
    }
    this.index = this.list._size + 1;
    this.current = default(T);
    return false;
}

Die List.ForEach-Methode ist viel kürzer als die MoveNext-Methode - weitaus weniger Verarbeitung - führt mit größerer Wahrscheinlichkeit zu einer effizienten JIT-Methode.

Außerdem weist foreach () einen neuen Enumerator zu, egal was passiert. Der GC ist Ihr Freund, aber wenn Sie wiederholt dasselbe für jeden tun, werden dadurch mehr wegwerfbare Objekte erstellt als bei der Wiederverwendung desselben Delegaten - , ABER - Das ist wirklich ein Randfall. Bei der typischen Verwendung werden Sie keinen oder nur einen geringen Unterschied feststellen.

16
plinth

Ich kenne zwei obskure Dinge, die sie voneinander unterscheiden. 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 Stellvertreter möglicherweise auf das letzte Element der Liste:

    // A list of actions to execute later
    List<Action> actions = new List<Action>();

    // Numbers 0 to 9
    List<int> numbers = Enumerable.Range(0, 10).ToList();

    // Store an action that prints each number (WRONG!)
    foreach (int number in numbers)
        actions.Add(() => Console.WriteLine(number));

    // Run the actions, we actually print 10 copies of "9"
    foreach (Action action in actions)
        action();

    // So try again
    actions.Clear();

    // Store an action that prints each number (RIGHT!)
    numbers.ForEach(number =>
        actions.Add(() => Console.WriteLine(number)));

    // Run the actions
    foreach (Action action in actions)
        action();

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 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 hat der Ansatz der ForEach-Methode eine Einschränkung. Wenn Sie IEnumerable mithilfe von Yield Return implementieren, können Sie im Lambda keine Yield Return ausführen. Daher ist es mit dieser Methode nicht möglich, die Elemente in einer Sammlung zu durchlaufen, um Rückgabesachen zu erhalten. Sie müssen das Schlüsselwort foreach verwenden und das Problem mit dem Schließen umgehen, indem Sie manuell eine Kopie des aktuellen Schleifenwerts in der Schleife erstellen.

Mehr hier

12

Ich denke, der someList.ForEach() -Aufruf könnte leicht parallelisiert werden, wohingegen der normale foreach nicht so leicht parallel zu laufen ist. Sie können problemlos mehrere verschiedene Delegates auf verschiedenen Kernen ausführen, was mit einem normalen foreach nicht so einfach ist.
Nur meine 2 Cent

10

Du könntest den anonymen Delegierten nennen :-)

Und Sie können die Sekunde schreiben als:

someList.ForEach(s => s.ToUpper())

Was ich bevorzuge und viel Tipparbeit erspart.

Wie Joachim sagt, ist es einfacher, Parallelität auf die zweite Form anzuwenden.

3
Craig.Nicol

Hinter den Kulissen verwandelt sich der anonyme Delegat in eine tatsächliche Methode, sodass Sie mit der zweiten Wahl etwas Aufwand haben können, wenn der Compiler die Funktion nicht integriert. Darüber hinaus ändern sich alle lokalen Variablen, auf die im Rumpf des Beispiels für anonyme Delegaten verwiesen wird, aufgrund von Compilertricks, um zu verbergen, dass sie zu einer neuen Methode kompiliert werden. Weitere Informationen dazu, wie C # diese Magie bewirkt, finden Sie hier:

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx

2
jezell

Der gesamte ForEach-Bereich (Delegatfunktion) wird als einzelne Codezeile (Aufruf der Funktion) behandelt, und Sie können keine Haltepunkte setzen oder in den Code eintreten. Wenn eine nicht behandelte Ausnahme auftritt, wird der gesamte Block markiert.

2
Peter Shen

List.ForEach () wird als funktionaler angesehen.

List.ForEach() sagt, was Sie tun möchten. foreach(item in list) sagt auch genau, wie Sie es tun möchten. Dies lässt List.ForEach Die Möglichkeit, die Implementierung des wie -Teils in Zukunft zu ändern. Zum Beispiel könnte eine hypothetische zukünftige Version von .Net immer List.ForEach Parallel ausgeführt werden, unter der Annahme, dass zu diesem Zeitpunkt alle über eine Reihe von CPU-Kernen verfügen, die im Allgemeinen im Leerlauf sind.

Andererseits gibt Ihnen foreach (item in list) etwas mehr Kontrolle über die Schleife. Sie wissen beispielsweise, dass die Elemente in einer sequenziellen Reihenfolge durchlaufen werden, und Sie können leicht in die Mitte springen, wenn ein Element eine bestimmte Bedingung erfüllt.

2
Joel Coehoorn

Die ForEach-Funktion ist Mitglied der generischen Klassenliste.

Ich habe die folgende Erweiterung erstellt, um den internen Code zu reproduzieren:

public static class MyExtension<T>
    {
        public static void MyForEach(this IEnumerable<T> collection, Action<T> action)
        {
            foreach (T item in collection)
                action.Invoke(item);
        }
    }

Am Ende verwenden wir also ein normales foreach (oder eine Schleife, wenn Sie möchten).

Auf der anderen Seite ist die Verwendung einer Delegate-Funktion nur eine andere Möglichkeit, eine Funktion zu definieren:

delegate(string s) {
    <process the string>
}

ist äquivalent zu:

private static void myFunction(string s, <other variables...>)
{
   <process the string>
}

oder mit Labda-Ausdrücken:

(s) => <process the string>
1
Pablo Caballero

Die zweite Methode, die Sie gezeigt haben, verwendet eine Erweiterungsmethode, um die Delegierungsmethode 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 durchlaufen.

1
EFrank

Seien Sie vorsichtig, wenn Sie die generische .ForEach-Methode verlassen möchten - siehe diese Diskussion . Obwohl der Link zu sagen scheint, dass dieser Weg der schnellste ist. Ich weiß nicht warum - man könnte meinen, sie wären gleichwertig, wenn sie erst einmal kompiliert sind ...

1
Chris Kimpton