wake-up-neo.net

LINQ-Äquivalent von foreach für IEnumerable <T>

Ich würde gerne Folgendes in LINQ machen, aber ich kann nicht herausfinden wie:

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

Was ist die wahre Syntax?

645
tags2k

Es gibt keine ForEach-Erweiterung für IEnumerable; nur für List<T>. Du könntest es also tun

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

Alternativ schreiben Sie Ihre eigene ForEach-Erweiterungsmethode:

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

Fredrik hat den Fix bereitgestellt, es kann jedoch eine Überlegung wert sein, warum dies nicht im Rahmen ist. Ich glaube, die Idee ist, dass die LINQ-Abfrageoperatoren frei von Nebeneffekten sein sollten und zu einer einigermaßen funktionalen Betrachtungsweise der Welt passen. Offensichtlich ist ForEach genau das Gegenteil - ein rein Nebeneffekt-Konstrukt.

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

340
Jon Skeet

Update 17.07.2012: Offensichtlich wurde seit C # 5.0 das Verhalten von foreach, das unten beschrieben wird, geändert und " die Verwendung einer Iterationenvariable foreach 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 foreach-Schlüsselwort bevorzugen.

Das Problem mit "foreach" in C # vor 5.0 ist, dass es nicht mit der Art und Weise übereinstimmt, in der das Äquivalent "für Verständnis" in anderen Sprachen funktioniert, und mit der Erwartung, dass es funktioniert (persönliche Meinung) hier nur angegeben, weil andere ihre Meinung zur Lesbarkeit erwähnt haben). Siehe alle Fragen zu " Zugriff auf modifizierte Schließung " Sowie " Schließen der Schleifenvariablen, die als schädlich angesehen wird ". Dies ist nur "schädlich", da "foreach" in C # implementiert wird.

Nehmen Sie die folgenden Beispiele mit der funktional äquivalenten Erweiterungsmethode zu der in @Fredrik Kalseths Antwort.

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

Entschuldigung für das übermäßig erfundene Beispiel. Ich verwende Observable nur, weil es nicht weit hergeholt ist, so etwas zu tun. Offensichtlich gibt es bessere Wege, um dieses Beobachtbare zu schaffen. Ich versuche nur, einen Punkt zu demonstrieren. Normalerweise wird der Code, der für das Observable abonniert wurde, asynchron und möglicherweise in einem anderen Thread ausgeführt. Bei Verwendung von "foreach" kann dies zu sehr merkwürdigen und möglicherweise nicht deterministischen Ergebnissen führen.

Der folgende Test mit der Erweiterungsmethode "ForEach" besteht:

[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()));
}

Folgendes schlägt mit dem Fehler fehl:

Erwartet: Äquivalent zu <0, 1, 2, 3, 4, 5, 6, 7, 8, 9>.). Aber war: <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()));
}
35
drstevens

Sie können die Erweiterung FirstOrDefault() verwenden, die für IEnumerable<T> verfügbar ist. Wenn Sie false aus dem Prädikat zurückgeben, wird es für jedes Element ausgeführt, kümmert sich jedoch nicht darum, dass es tatsächlich keine Übereinstimmung findet. Dadurch wird der ToList()-Aufwand vermieden.

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

Ich habe Fredriks Methode genommen und den Rückgabetyp geändert.

Auf diese Weise unterstützt die Methode verzögerte Ausführung wie andere LINQ-Methoden.

EDIT: Wenn dies nicht klar war, muss jede Verwendung dieser Methode mit ToList () oder auf eine andere Weise enden, um die Methode zu zwingen, die vollständige Aufzählung zu bearbeiten. Andernfalls wird die Aktion nicht ausgeführt!

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

Und hier ist der Test, um zu helfen, es zu sehen:

[Test]
public void TestDefferedExecutionOfIEnumerableForEach()
{
    IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};

    var sb = new StringBuilder();

    enumerable
        .ForEach(c => sb.Append("1"))
        .ForEach(c => sb.Append("2"))
        .ToList();

    Assert.That(sb.ToString(), Is.EqualTo("121212"));
}

Wenn Sie am Ende die ToList () entfernen, wird der Test fehlschlagen, da der StringBuilder eine leere Zeichenfolge enthält. Dies liegt daran, dass keine Methode das ForEach zum Auflisten zwang.

19
Dor Rotman

Halten Sie Ihre Nebenwirkungen von IEnumerable fern

Ich würde gerne Folgendes in LINQ machen, aber ich kann nicht herausfinden, wie:

Wie andere auf hier und im Ausland hingewiesen haben, wird erwartet, dass LINQ- und IEnumerable-Methoden nebenwirkungsfrei sind. 

Möchten Sie wirklich mit jedem Element im IEnumerable "etwas tun"? Dann ist foreach die beste Wahl. Die Menschen sind nicht überrascht, wenn hier Nebenwirkungen auftreten. 

foreach (var i in items) i.DoStuff();

Ich wette, Sie wollen keinen Nebeneffekt

Nach meiner Erfahrung sind Nebenwirkungen normalerweise jedoch nicht erforderlich. Meistens gibt es eine einfache LINQ-Abfrage, die darauf wartet, entdeckt zu werden, begleitet von einer Antwort von StackOverflow.com von Jon Skeet, Eric Lippert oder Marc Gravell, in der erklärt wird, was Sie tun möchten.

Einige Beispiele

Wenn Sie tatsächlich nur einen Wert sammeln (akkumulieren), sollten Sie die Erweiterungsmethode Aggregate in Betracht ziehen.

items.Aggregate(initial, (acc, x) => ComputeAccumulatedValue(acc, x));

Vielleicht möchten Sie aus den vorhandenen Werten eine neue IEnumerable erstellen. 

items.Select(x => Transform(x));

Oder möchten Sie vielleicht eine Nachschlagetabelle erstellen:

items.ToLookup(x, x => GetTheKey(x))

Die Liste der Möglichkeiten (Wortspiel nicht ganz beabsichtigt) geht weiter und weiter. 

16
cdiggins

Wenn Sie als Aufzählungsrollen fungieren möchten, sollten Sie jeden Artikel ausgeben.

public static class EnumerableExtensions
{
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
    {
        foreach (var item in enumeration)
        {
            action(item);
            yield return item;
        }
    }
}
9
regisbsb

Es gibt eine experimentelle Version von _ von Interactive Extensions to LINQ (auch bei NuGet , siehe Profil von RxTeams für weitere Links. Das Channel 9 Video erklärt es gut.

Seine Dokumente werden nur im XML-Format bereitgestellt. Ich habe diese Dokumentation in Sandcastle ausgeführt, um ein lesbareres Format zu erhalten. Entpacken Sie das Dokumentenarchiv und suchen Sie nach index.html.

Neben vielen anderen Leckereien bietet es die erwartete ForEach-Implementierung. Damit können Sie Code wie folgt schreiben:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };

numbers.ForEach(x => Console.WriteLine(x*x));
8
John Wigger

Laut PLINQ (verfügbar seit .Net 4.0) können Sie eine

IEnumerable<T>.AsParallel().ForAll() 

eine parallele foreach-Schleife auf einem IEnumerable ausführen.

7
Wolf5

Der Zweck von ForEach ist es, Nebenwirkungen zu verursachen. IEnumerable dient zur verzögerten Aufzählung einer Menge.

Dieser konzeptionelle Unterschied ist deutlich sichtbar, wenn Sie ihn betrachten.

SomeEnumerable.ForEach(item=>DataStore.Synchronize(item));

Dies wird erst ausgeführt, wenn Sie eine "Zählung" oder eine "ToList ()" oder etwas darauf durchführen. Es ist eindeutig nicht das, was ausgedrückt wird.

Sie sollten die IEnumerable-Erweiterungen zum Einrichten von Iterationsketten verwenden und den Inhalt anhand der jeweiligen Quellen und Bedingungen definieren. Ausdrucksbäume sind mächtig und effizient, aber Sie sollten lernen, ihre Natur zu schätzen. Und das nicht nur zum Programmieren, um ein paar Zeichen zu sparen, die die träge Bewertung überschreiben.

6
Tormod

Viele Leute haben es erwähnt, aber ich musste es aufschreiben. Ist das nicht am klarsten/lesbarsten?

IEnumerable<Item> items = GetItems();
foreach (var item in items) item.DoStuff();

Kurz und einfach (st).

4
Nenad

Wie bereits zahlreiche Antworten zeigen, können Sie eine solche Erweiterungsmethode problemlos selbst hinzufügen. Wenn Sie dies jedoch nicht möchten, obwohl mir dies in der BCL nicht bekannt ist, gibt es im System-Namespace noch eine Option, wenn Sie bereits einen Verweis auf Reactive Extension (und Wenn nicht, sollten Sie haben):

using System.Reactive.Linq;

items.ToObservable().Subscribe(i => i.DoStuff());

Obwohl die Methodennamen etwas unterschiedlich sind, ist das Endergebnis genau das, wonach Sie suchen.

4
Mark Seemann

ForEach kann auch Chained sein, einfach nach der Aktion wieder auf die Pileline setzen. fließend bleiben


Employees.ForEach(e=>e.Act_A)
         .ForEach(e=>e.Act_B)
         .ForEach(e=>e.Act_C);

Orders  //just for demo
    .ForEach(o=> o.EmailBuyer() )
    .ForEach(o=> o.ProcessBilling() )
    .ForEach(o=> o.ProcessShipping());


//conditional
Employees
    .ForEach(e=> {  if(e.Salary<1000) e.Raise(0.10);})
    .ForEach(e=> {  if(e.Age   >70  ) e.Retire();});

Eine eifrige Version der Implementierung.

public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enu, Action<T> action)
{
    foreach (T item in enu) action(item);
    return enu; // make action Chainable/Fluent
}

Bearbeiten: Eine Lazy Version verwendet Yield Return, wie dies .

public static IEnumerable<T> ForEachLazy<T>(this IEnumerable<T> enu, Action<T> action)
{
    foreach (var item in enu)
    {
        action(item);
        yield return item;
    }
}

Die Lazy-Version MUSS materialisiert werden, ToList () zum Beispiel, sonst passiert nichts. siehe unten tolle Kommentare von ToolmakerSteve.

IQueryable<Product> query = Products.Where(...);
query.ForEachLazy(t => t.Price = t.Price + 1.00)
    .ToList(); //without this line, below SubmitChanges() does nothing.
SubmitChanges();

Ich behalte sowohl ForEach () als auch ForEachLazy () in meiner Bibliothek.

2
Rm558

Jetzt haben wir die Möglichkeit, ...

        ParallelOptions parallelOptions = new ParallelOptions();
        parallelOptions.MaxDegreeOfParallelism = 4;
#if DEBUG
        parallelOptions.MaxDegreeOfParallelism = 1;
#endif
        Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));

Dies eröffnet natürlich eine ganz neue Dose Fadenwürmer.

ps (Sorry über die Schriften, das hat das System entschieden)

2
Paulustrious

Noch ein ForEach Beispiel

public static IList<AddressEntry> MapToDomain(IList<AddressModel> addresses)
{
    var workingAddresses = new List<AddressEntry>();

    addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a)));

    return workingAddresses;
}
1
neil martin

Inspiriert von Jon Skeet habe ich seine Lösung um Folgendes erweitert:

Erweiterungsmethode:

public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector)
{
    foreach (var item in source)
    {
        var target = keySelector(item);
        applyBehavior(target);
    }
}

Klient:

var jobs = new List<Job>() 
    { 
        new Job { Id = "XAML Developer" }, 
        new Job { Id = "Assassin" }, 
        new Job { Id = "Narco Trafficker" }
    };

jobs.Execute(ApplyFilter, j => j.Id);

...

    public void ApplyFilter(string filterId)
    {
        Debug.WriteLine(filterId);
    }
1
Scott Nimrod

Ich stimme der Auffassung nicht zu, dass die Link-Erweiterungsmethoden nebenwirkungsfrei sein sollten (nicht nur, weil dies nicht der Fall ist, jeder Delegierte kann Nebenwirkungen haben).

Folgendes berücksichtigen:

   public class Element {}

   public Enum ProcessType
   {
      This = 0, That = 1, SomethingElse = 2
   }

   public class Class1
   {
      private Dictionary<ProcessType, Action<Element>> actions = 
         new Dictionary<ProcessType,Action<Element>>();

      public Class1()
      {
         actions.Add( ProcessType.This, DoThis );
         actions.Add( ProcessType.That, DoThat );
         actions.Add( ProcessType.SomethingElse, DoSomethingElse );
      }

      // Element actions:

      // This example defines 3 distict actions
      // that can be applied to individual elements,
      // But for the sake of the argument, make
      // no assumption about how many distict
      // actions there may, and that there could
      // possibly be many more.

      public void DoThis( Element element )
      {
         // Do something to element
      }

      public void DoThat( Element element )
      {
         // Do something to element
      }

      public void DoSomethingElse( Element element )
      {
         // Do something to element
      }

      public void Apply( ProcessType processType, IEnumerable<Element> elements )
      {
         Action<Element> action = null;
         if( ! actions.TryGetValue( processType, out action ) )
            throw new ArgumentException("processType");
         foreach( element in elements ) 
            action(element);
      }
   }

Was das Beispiel zeigt, ist wirklich nur eine Art Late-Binding, bei dem man eine von vielen möglichen Aktionen mit Nebeneffekten auf eine Folge von Elementen aufrufen kann, ohne ein großes Schalterkonstrukt schreiben zu müssen, um den Wert zu dekodieren, der die Aktion definiert und übersetzt es in seine entsprechende Methode.

0
caddzooks

MoreLinq hat IEnumerable<T>.ForEach und eine Menge anderer nützlicher Erweiterungen. Es ist wahrscheinlich nicht nur für ForEach eine Abhängigkeit wert, aber es gibt eine Menge nützlicher Sachen.

https://www.nuget.org/packages/morelinq/

https://github.com/morelinq/MoreLINQ

0
solublefish

Um fließend zu bleiben, kann man einen solchen Trick anwenden:

GetItems()
    .Select(i => new Action(i.DoStuf)))
    .Aggregate((a, b) => a + b)
    .Invoke();
0
Alex

Für VB.NET sollten Sie Folgendes verwenden:

listVariable.ForEach(Sub(i) i.Property = "Value")
0

Diese "funktionale Herangehensweise" -Abstraktion leidet enorm. Nichts auf der Sprachebene verhindert Nebenwirkungen. Wenn Sie Ihren Lambda/Delegate-Befehl für jedes Element im Container aufrufen können, erhalten Sie das Verhalten "ForEach".

Hier zum Beispiel eine Möglichkeit, srcDictionary in destDictionary zusammenzuführen (wenn der Schlüssel bereits vorhanden ist - überschreibt)

Dies ist ein Hack und sollte in keinem Produktionscode verwendet werden.

var b = srcDictionary.Select(
                             x=>
                                {
                                  destDictionary[x.Key] = x.Value;
                                  return true;
                                }
                             ).Count();
0
Zar Shardan