it-swarm.com.de

Warum gibt es keine ForEach-Erweiterungsmethode für IEnumerable?

Inspiriert von einer anderen Frage nach der fehlenden Zip -Funktion:

Warum gibt es in der Klasse ForEach keine Enumerable -Erweiterungsmethode? Oder irgendwo? Die einzige Klasse, die eine ForEach -Methode erhält, ist List<>. Gibt es einen Grund, warum es fehlt (Leistung)?

355

Es gibt bereits eine foreach-Anweisung in der Sprache, die die meiste Zeit die Arbeit erledigt.

Ich würde es hassen, folgendes zu sehen:

list.ForEach( item =>
{
    item.DoSomething();
} );

Anstatt von:

foreach(Item item in list)
{
     item.DoSomething();
}

Letzteres ist klarer und leichter zu lesen in den meisten Situationen, obwohl es möglicherweise etwas länger zu tippen ist.

Ich muss jedoch zugeben, dass ich meine Haltung zu diesem Thema geändert habe. Eine ForEach () -Erweiterungsmethode wäre in manchen Situationen in der Tat nützlich.

Hier sind die Hauptunterschiede zwischen der Anweisung und der Methode:

  • Typprüfung: foreach wird zur Laufzeit ausgeführt, ForEach () befindet sich zur Kompilierungszeit (Big Plus!)
  • Die Syntax zum Aufrufen eines Delegaten ist in der Tat viel einfacher: objects.ForEach (DoSomething);
  • ForEach () könnte verkettet werden: Obwohl die Bösartigkeit/Nützlichkeit einer solchen Funktion zur Diskussion steht.

Das sind alles großartige Punkte, die von vielen hier gemacht wurden, und ich kann sehen, warum den Leuten die Funktion fehlt. Es würde mir nichts ausmachen, wenn Microsoft in der nächsten Framework-Iteration eine Standard-ForEach-Methode hinzufügt.

187
Coincoin

ForEach-Methode wurde vor LINQ hinzugefügt. Wenn Sie die Erweiterung ForEach hinzufügen, wird sie aufgrund von Einschränkungen für Erweiterungsmethoden niemals für List-Instanzen aufgerufen. Ich denke, der Grund, warum es nicht hinzugefügt wurde, ist, dass es nicht in bestehende eingreift.

Wenn Sie diese kleine Nice-Funktion jedoch wirklich vermissen, können Sie Ihre eigene Version herausbringen

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source) 
        action(element);
}
72
aku

Sie könnten diese Erweiterungsmethode schreiben:

// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
    foreach (var e in source)
    {
        action(e);
        yield return e;
    }
}

Profis

Ermöglicht Verketten:

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);

Nachteile

Es wird eigentlich nichts tun, bis Sie etwas tun, um die Iteration zu erzwingen. Aus diesem Grund sollte es nicht .ForEach() genannt werden. Sie könnten .ToList() am Ende schreiben, oder Sie könnten auch diese Erweiterungsmethode schreiben:

// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
    foreach (var e in source)
    {
        // do nothing
        ;
    }

    return source;
}

Dies kann eine Abweichung von den Versand-C # -Bibliotheken zu bedeutend sein. Leser, die mit Ihren Erweiterungsmethoden nicht vertraut sind, wissen nicht, wie sie mit Ihrem Code umgehen sollen.

50
Jay Bazuzi

Die Diskussion hier gibt die Antwort:

Tatsächlich hing die spezifische Diskussion, die ich miterlebte, tatsächlich von der funktionalen Reinheit ab. In einem Ausdruck wird häufig davon ausgegangen, dass es keine Nebenwirkungen gibt. ForEach zu haben, ist spezifisch, Nebenwirkungen einzuladen, anstatt sie nur in Kauf zu nehmen. - Keith Farmer (Partner)

Grundsätzlich wurde die Entscheidung getroffen, die Erweiterungsmethoden funktional "rein" zu halten. Ein ForEach würde bei Verwendung der Enumerable-Erweiterungsmethoden zu Nebenwirkungen führen, was nicht beabsichtigt war.

33
mancaus

Ich bin damit einverstanden, dass es in den meisten Fällen besser ist, das eingebaute Konstrukt foreach zu verwenden, finde die Verwendung dieser Variation der Erweiterung ForEach <> jedoch etwas angenehmer, als den Index regelmäßig verwalten zu müssen foreach ich selbst:

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
    if (action == null) throw new ArgumentNullException("action");

    var index = 0;

    foreach (var elem in list)
        action(index++, elem);

    return index;
}
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));

Würde dir geben:

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry
16
Chris Zwiryk

Eine Problemumgehung besteht darin, .ToList().ForEach(x => ...) zu schreiben.

Profis

Einfach zu verstehen - der Leser muss nur wissen, was mit C # geliefert wird, keine zusätzlichen Erweiterungsmethoden.

Syntaktisches Rauschen ist sehr leise (fügt nur ein wenig extranösen Code hinzu).

Kostet normalerweise keinen zusätzlichen Speicher, da eine native .ForEach() ohnehin die gesamte Sammlung realisieren müsste.

cons

Die Reihenfolge der Operationen ist nicht ideal. Ich möchte lieber ein Element erkennen, danach handeln und dann wiederholen. Dieser Code erkennt zuerst alle Elemente und wirkt dann nacheinander auf sie ein.

Wenn das Realisieren der Liste eine Ausnahme auslöst, können Sie niemals auf ein einzelnes Element einwirken.

Wenn die Aufzählung unendlich ist (wie die natürlichen Zahlen), haben Sie kein Glück.

14
Jay Bazuzi

Ich habe mich immer gefragt, warum ich das immer bei mir habe:

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    foreach (var item in col)
    {
        action(item);
    }
}

Schöne kleine Erweiterungsmethode.

13
Aaron Powell

Es gab also viele Kommentare zu der Tatsache, dass eine ForEach-Erweiterungsmethode nicht geeignet ist, da sie keinen Wert wie die LINQ-Erweiterungsmethoden zurückgibt. Dies ist zwar eine Tatsachenaussage, aber nicht ganz richtig.

Die LINQ-Erweiterungsmethoden geben alle einen Wert zurück, sodass sie miteinander verkettet werden können:

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

Nur weil LINQ mithilfe von Erweiterungsmethoden implementiert wird, bedeutet dies nicht, dass die Erweiterungsmethoden muss auf die gleiche Weise verwendet werden und einen Wert zurückgeben. Das Schreiben einer Erweiterungsmethode, um allgemeine Funktionen verfügbar zu machen, die keinen Wert zurückgeben, ist eine einwandfreie Verwendung.

Die spezifische Argumentation zu ForEach ist, dass es auf der Grundlage der Einschränkungen für Erweiterungsmethoden (nämlich, dass eine Erweiterungsmethode niemals eine geerbte Methode mit derselben Signatur überschreibt) möglicherweise gibt eine Situation, in der die benutzerdefinierte Erweiterungsmethode für alle Klassen verfügbar ist, die das Element IEnumerable<T> außer List<T>. Dies kann zu Verwirrung führen, wenn sich die Methoden je nachdem, ob die Erweiterungsmethode oder die Vererbungsmethode aufgerufen wird, unterschiedlich verhalten.

8
Scott Dorman

Sie können das (verkettbare, aber träge ausgewertete) Select verwenden, indem Sie zuerst Ihre Operation ausführen und dann die Identität zurückgeben (oder etwas anderes, wenn Sie es vorziehen).

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

Sie müssen sicherstellen, dass es weiterhin ausgewertet wird, entweder mit Count() (die billigste Operation, um afaik aufzuzählen) oder mit einer anderen Operation, die Sie sowieso benötigen.

Ich würde es gerne sehen, wenn es in die Standardbibliothek aufgenommen wird:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

Der obige Code wird dann zu people.WithLazySideEffect(p => Console.WriteLine(p)), was praktisch foreach entspricht, aber faul und verkettbar ist.

7
Martijn

@Coincoin

Die wahre Stärke der foreach-Erweiterungsmethode besteht in der Wiederverwendbarkeit des Action<> ohne unnötige Methoden zum Code hinzuzufügen. Angenommen, Sie haben 10 Listen und möchten dieselbe Logik für diese Listen ausführen. Eine entsprechende Funktion passt nicht in Ihre Klasse und wird nicht wiederverwendet. Anstatt zehn for-Schleifen oder eine generische Funktion zu haben, die offensichtlich ein nicht dazugehöriger Helfer ist, können Sie Ihre gesamte Logik an einem Ort aufbewahren (das Action<>. So werden Dutzende von Zeilen durch ersetzt

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

usw...

Die Logik ist an einem Ort und Sie haben Ihre Klasse nicht verschmutzt.

4
diehard2

Beachten Sie, dass das MoreLINQ NuGet die von Ihnen gesuchte ForEach -Erweiterungsmethode (sowie eine Pipe -Methode) bereitstellt der den Delegierten ausführt und sein Ergebnis liefert). Sehen:

4
Dave Clausen

Ich habe einen Blog-Beitrag darüber geschrieben: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

Sie können hier abstimmen, ob diese Methode in .NET 4.0 angezeigt werden soll: http://connect.Microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=27909

2
Kirill Osenkov

Ich möchte auf Akus Antwort erweitern.

Wenn Sie eine Methode nur zum Zweck der Nebenwirkung aufrufen möchten, ohne zuerst die gesamte Aufzählung zu wiederholen, können Sie dies tun:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}
2
fredefox

In 3.5 sind alle zu IEnumerable hinzugefügten Erweiterungsmethoden für die LINQ-Unterstützung vorgesehen (beachten Sie, dass sie in der System.Linq.Enumerable-Klasse definiert sind). In diesem Beitrag erkläre ich, warum foreach nicht zu LINQ gehört: Vorhandene LINQ-Erweiterungsmethode ähnlich wie Parallel.For?

2
Neil

Bin ich es oder ist die Liste <T> .Foreach wurde von Linq so ziemlich obsolet gemacht. Ursprünglich gab es

foreach(X x in Y) 

dabei musste Y nur IEnumerable (vor 2.0) sein und einen GetEnumerator () implementieren. Wenn Sie sich die erzeugte MSIL ansehen, können Sie feststellen, dass sie genau so ist wie

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

(Siehe http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx für die MSIL)

Dann kamen in DotNet2.0 Generics und die Liste. Foreach hat mich immer als Implementierung des Vistor-Musters empfunden (siehe Design Patterns von Gamma, Helm, Johnson, Vlissides).

Jetzt können wir natürlich in 3.5 stattdessen ein Lambda verwenden, um den gleichen Effekt zu erzielen. Zum Beispiel versuchen Sie http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and- linq-oh-my /

2
user18784

Die meisten LINQ-Erweiterungsmethoden geben Ergebnisse zurück. ForEach passt nicht in dieses Muster, da es nichts zurückgibt.

2
leppie

Dies liegt zum Teil daran, dass die Sprachdesigner aus philosophischer Sicht nicht damit einverstanden sind.

  • Ein Feature nicht zu haben (und zu testen ...) ist weniger Arbeit als ein Feature.
  • Es ist nicht wirklich kürzer (es gibt einige vorübergehende Funktionsfälle, in denen es vorkommt, aber das wäre nicht die primäre Verwendung).
  • Das Ziel ist es, Nebenwirkungen zu haben, um die es bei linq nicht geht.
  • Warum gibt es eine andere Möglichkeit, das Gleiche zu tun wie eine Funktion, die wir bereits haben? (für jedes Keyword)

https://blogs.msdn.Microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

2
McKay

Wenn Sie F # haben (das wird in der nächsten Version von .NET sein), können Sie verwenden

Seq.iter doSomething myIEnumerable

2
TraumaPony

Sie können select verwenden, wenn Sie etwas zurückgeben möchten. Wenn Sie dies nicht tun, können Sie zuerst ToList verwenden, da Sie wahrscheinlich nichts in der Auflistung ändern möchten.

2
Paco

Um Foreach zu verwenden, sollte Ihre Liste in den Primärspeicher geladen werden. Aufgrund des verzögerten Ladens von IEnumerable wird ForEach jedoch nicht in IEnumerable bereitgestellt.

Durch eifriges Laden (mit ToList ()) können Sie Ihre Liste jedoch in den Speicher laden und ForEach nutzen.

1
Arjun Bhalodiya

Bisher hat noch niemand darauf hingewiesen, dass ForEach <T> zu einer Überprüfung des Kompilierungstyps führt, bei der das Schlüsselwort foreach zur Laufzeit überprüft wird.

Nach einigen Umgestaltungen, bei denen beide Methoden im Code verwendet wurden, bevorzuge ich .ForEach, da ich nach Testfehlern/Laufzeitfehlern suchen musste, um die foreach-Probleme zu finden.

0
Squirrel