877 Stimmen

LINQ-Entsprechung von foreach für IEnumerable<T>

Ich möchte das Äquivalent der folgenden in LINQ zu tun, aber ich kann nicht herausfinden, wie:

IEnumerable<Item> items = GetItems();
items.ForEach(i => i.DoStuff());

Was ist die richtige Syntax?

4 Stimmen

@pst: Ich habe diese Erweiterungsmethoden immer blindlings in meine Projekte gesteckt. Danke für diesen Artikel.

3 Stimmen

Es gibt MehrLINQ die eine ForEach Erweiterung .

0 Stimmen

Hier ist eine alternative Idee, wie dies könnte möglich sein: visualstudio.uservoice.com/forums/121579-visual-studio-2015/

1059voto

Fredrik Kalseth Punkte 13273

Es gibt keine ForEach-Erweiterung für IEnumerable nur für List<T> . Sie könnten also Folgendes tun

items.ToList().ForEach(i => i.DoStuff());

Alternativ können Sie auch Ihre eigene ForEach-Erweiterungsmethode schreiben:

public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
    foreach(T item in enumeration)
    {
        action(item);
    }
}

0 Stimmen

Früher hatte ich eine solche Methode in meinem Code, aber eigentlich löst ToList().ForEach() das Problem, und ich glaube, dass die Antwort von Jon (siehe unten) richtiger ist.

266 Stimmen

Seien Sie vorsichtig mit ToList(), denn ToList() erstellt eine Kopie der Sequenz, was zu Leistungs- und Speicherproblemen führen kann.

17 Stimmen

Es gibt einige Gründe, warum ".Foreach()" besser ist. 1. Multi-threading, möglich durch PLINQ. 2. Ausnahmen, Sie möchten vielleicht alle Ausnahmen in der Schleife sammeln und auf einmal auslösen; und sie sind Rauschcodes.

428voto

Jon Skeet Punkte 1325502

Fredrik hat die Lösung bereitgestellt, aber es könnte sich lohnen, darüber nachzudenken, warum dies nicht von Anfang an im Framework enthalten ist. Ich glaube, die Idee ist, dass die LINQ-Abfrageoperatoren frei von Seiteneffekten sein sollten und zu einer einigermaßen funktionalen Sichtweise auf die Welt passen. Offensichtlich ist ForEach genau das Gegenteil - ein rein nebenwirkungsbasiertes Konstrukt.

Das soll nicht heißen, dass dies eine schlechte Sache ist - ich denke nur über die philosophischen Gründe für diese Entscheidung nach.

3 Stimmen

In dieser Frage wird dieselbe Frage gestellt, und es wird ein Beitrag von "den Machthabern" geliefert: stackoverflow.com/questions/858978/

9 Stimmen

Es gibt jedoch eine sehr nützliche Sache, die LINQ-Funktionen typischerweise bieten, die foreach () nicht hat - die anonyme Funktion, die von Linq-Funktionen genommen wird, kann auch einen Index als Parameter nehmen, während Sie mit foreach das klassische for (int i..)-Konstrukt wieder aufbauen müssen. Und funktionale Sprachen müssen Iterationen mit Seiteneffekten zulassen - sonst könnte man nie ein Jota Arbeit mit ihnen machen :) Ich möchte darauf hinweisen, dass die typische funktionale Methode darin besteht, das ursprüngliche enumerable unverändert zurückzugeben.

0 Stimmen

@Walt: Wenn eine typische funktionale Methode das ursprüngliche enumerable zurückgibt und hat nicht Nebenwirkungen haben (die typisch funktionale Methoden nicht), dann hätte es überhaupt keine Auswirkungen. Vielleicht könnten Sie Ihre Bemerkung präzisieren?

41voto

drstevens Punkte 2893

Update 7/17/2012: Offenbar ist ab C# 5.0 das Verhalten von foreach wie unten beschrieben, geändert und " die Verwendung eines foreach Iterationsvariable in einem verschachtelten Lambda-Ausdruck führt nicht mehr zu unerwarteten Ergebnissen. " Diese Antwort gilt nicht für C# 5.0.

@John Skeet und alle, die das Schlüsselwort foreach bevorzugen.

Das Problem mit "foreach" in C# vor 5.0 ist, dass es nicht damit übereinstimmt, wie das Äquivalent "zum Verstehen" in anderen Sprachen funktioniert und wie ich es erwarten würde (persönliche Meinung, die hier nur deshalb geäußert wird, weil andere ihre Meinung zur Lesbarkeit geäußert haben). Siehe alle Fragen zu " Zugang zum geänderten Verschluss " als auch " Schließen über die als schädlich angesehene Schleifenvariable ". Dies ist nur "schädlich" wegen der Art und Weise, wie "foreach" in C# implementiert ist.

Nehmen Sie die folgenden Beispiele unter Verwendung der funktional äquivalenten Erweiterungsmethode zu der in der Antwort von @Fredrik Kalseth.

public static class Enumerables
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (T item in @this)
        {
            action(item);
        }
    }
}

Ich entschuldige mich für das übertrieben konstruierte Beispiel. Ich verwende Observable nur, weil es nicht ganz so weit hergeholt ist, so etwas zu tun. Offensichtlich gibt es bessere Möglichkeiten, diese Observable zu erstellen, ich versuche nur, einen Punkt zu demonstrieren. Normalerweise wird der Code, der die Observable abonniert hat, asynchron und möglicherweise in einem anderen Thread ausgeführt. Bei der Verwendung von "foreach" könnte dies zu sehr seltsamen und potenziell nicht deterministischen Ergebnissen führen.

Der folgende Test mit der Erweiterungsmethode "ForEach" ist erfolgreich:

[Test]
public void ForEachExtensionWin()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                values.ForEach(value => 
                                    source.OnNext(() => value));

                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Win
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

Das folgende schlägt mit der Fehlermeldung fehl:

Erwartet: entspricht < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 > War aber: < 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 >

[Test]
public void ForEachKeywordFail()
{
    //Yes, I know there is an Observable.Range.
    var values = Enumerable.Range(0, 10);

    var observable = Observable.Create<Func<int>>(source =>
                            {
                                foreach (var value in values)
                                {
                                    //If you have resharper, notice the warning
                                    source.OnNext(() => value);
                                }
                                source.OnCompleted();
                                return () => { };
                            });

    //Simulate subscribing and evaluating Funcs
    var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();

    //Fail
    Assert.That(evaluatedObservable, 
        Is.EquivalentTo(values.ToList()));
}

0 Stimmen

Was besagt die Warnung des Resharpers?

1 Stimmen

@reggaeguitar Ich habe seit 7 oder 8 Jahren nicht mehr mit C# gearbeitet, bevor C# 5.0 veröffentlicht wurde, das das hier beschriebene Verhalten änderte. Zu der Zeit gab es diese Warnung stackoverflow.com/questions/1688465/ . Ich bezweifle allerdings, dass sie noch davor warnt.

38voto

Rhames Punkte 671

Sie könnten die FirstOrDefault() Erweiterung, die verfügbar ist für IEnumerable<T> . Durch die Rückkehr false aus dem Prädikat, wird es für jedes Element ausgeführt, wobei es egal ist, ob es tatsächlich eine Übereinstimmung findet oder nicht. Dadurch wird die ToList() Overhead.

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });

14 Stimmen

Das sehe ich auch so. Es könnte ein cleverer kleiner Hack sein, aber auf den ersten Blick ist dieser Code nicht klar, was er tut, zumindest im Vergleich zu einer normalen foreach-Schleife.

34 Stimmen

Ich musste die Bewertung herabsetzen, denn während technisch richtig, werden Ihr zukünftiges Ich und alle Ihre Nachfolger Sie für immer hassen, weil Sie statt foreach(Item i in GetItems()) { i.DoStuff();} Sie haben mehr Zeichen genommen und es extrem verwirrend gemacht

15 Stimmen

Ok, aber ein besser lesbarer Hack: items.All(i => { i.DoStuff(); return true; }

32voto

l33t Punkte 15267

So viele Antworten, aber ALLE lassen ein sehr wichtiges Problem bei einer benutzerdefinierten Lösung außer Acht genérico ForEach Erweiterung: Leistung! Und ganz besonders, Speichernutzung y GC .

Betrachten Sie das folgende Beispiel. Zielsetzung .NET Framework 4.7.2 o .NET Core 3.1.401 ist die Konfiguration Release und Plattform ist Any CPU .

public static class Enumerables
{
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (T item in @this)
        {
            action(item);
        }
    }
}

class Program
{
    private static void NoOp(int value) {}

    static void Main(string[] args)
    {
        var list = Enumerable.Range(0, 10).ToList();
        for (int i = 0; i < 1000000; i++)
        {
            // WithLinq(list);
            // WithoutLinqNoGood(list);
            WithoutLinq(list);
        }
    }

    private static void WithoutLinq(List<int> list)
    {
        foreach (var item in list)
        {
            NoOp(item);
        }
    }

    private static void WithLinq(IEnumerable<int> list) => list.ForEach(NoOp);

    private static void WithoutLinqNoGood(IEnumerable<int> enumerable)
    {
        foreach (var item in enumerable)
        {
            NoOp(item);
        }
    }
}

Auf den ersten Blick sollten alle drei Varianten gleich gut abschneiden. Wenn jedoch die ForEach Erweiterungsmethode wird many genannt, viele Manchmal landet man bei Müll, der einen teuren GC nach sich zieht. In der Tat, mit dieser ForEach Erweiterungsmethode auf einem heißen Pfad hat sich in unserer schleifenintensiven Anwendung als völlig leistungsmindernd erwiesen.

Ähnlich verhält es sich mit dem schwach typisierten foreach Schleife produziert zwar auch Müll, ist aber trotzdem schneller und weniger speicherintensiv als die ForEach Erweiterung (die ebenfalls unter einer Delegiertenzuweisung ).

Stark typisiertes foreach: Speicherverbrauch

No allocations. No GC

Schwach typisiertes foreach: Speicherverbrauch

enter image description here

ForEach-Erweiterung: Speicherverbrauch

Lots of allocations. Heavy GC.

Analyse

Für eine stark typisierte foreach kann der Compiler jeden optimierten Enumerator (z. B. wertbasiert) einer Klasse verwenden, während ein generischer ForEach Erweiterung muss auf einen generischen Enumerator zurückgreifen, der bei jedem Lauf zugewiesen wird. Darüber hinaus wird auch der eigentliche Delegat eine zusätzliche Zuweisung erfordern.

Ähnlich schlechte Ergebnisse würden Sie mit dem WithoutLinqNoGood Methode. Dort ist das Argument vom Typ IEnumerable<int> anstelle von List<int> was dieselbe Art der Zählerzuweisung impliziert.

Nachstehend sind die relevanten Unterschiede in IL . Ein wertbasierter Enumerator ist sicherlich vorzuziehen!

IL_0001:  callvirt   instance class
          [mscorlib]System.Collections.Generic.IEnumerator`1<!0> 
          class [mscorlib]System.Collections.Generic.IEnumerable`1<!!T>::GetEnumerator()

gegen

IL_0001:  callvirt   instance valuetype
          [mscorlib]System.Collections.Generic.List`1/Enumerator<!0>
          class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()

Schlussfolgerung

Der OP fragte, wie man anruft ForEach() an einem IEnumerable<T> . Die ursprüngliche Antwort zeigt deutlich, wie sie puede getan werden. Sicher kann man es tun, aber meine Antwort zeigt deutlich, dass man es nicht tun sollte.

Das gleiche Verhalten wurde beim Anvisieren von .NET Core 3.1.401 (Kompilieren mit Visual Studio 16.7.2 ).

1 Stimmen

Sehr klare Analyse, bin mir nicht sicher, ob Sie auch so gut über die empfohlenen Maßnahmen berichten. Selbst wenn es ein Link zu einem Artikel über bewährte Verfahren wäre wirklich hilfreich sein. Ich vermute, Sie meinen, verwenden Sie nicht linq für diese Verwendung foreach(x in y){ f(x) }

0 Stimmen

Seltsam: Es gibt einen Unterschied zwischen dem Aufruf von getenumerator für eine als Schnittstelle deklarierte Variable und einer als Schnittstelle deklarierten Variable, die anscheinend Müll verursacht. Da beide die gleiche tatsächliche Implementierung aufrufen würden. Abgesehen davon denke ich, wenn man eine Sammlung so oft scannt, hat der Algorithmus eine wirklich schlechte Rechenkomplexität. Ich frage mich also, in welchen Szenarien dies relevant ist und nicht einfach durch das Schreiben eines geeigneten Algorithmus verbessert werden kann.

0 Stimmen

Das ist in jedem erdenklichen Fall relevant. Es sei denn, Ihre Programme sind zu 100 % schleifenfrei.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X