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

717

Ich möchte in LINQ das Gleiche tun, aber ich kann nicht herausfinden, wie:

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

Was ist die wahre Syntax?

tags2k
quelle
99
Betrachten Sie "foreach" vs. "ForEach" von Eric Lippert. Es bietet eine gute Rechtfertigung dafür, a nicht zu verwenden / zu liefern ForEach().
4
@pst: Früher habe ich diese Erweiterungsmethoden blind in meine Projekte gesteckt. Danke für diesen Artikel.
Robin Maben
2
Es gibt MoreLINQ mit einer ForEachErweiterung .
Uwe Keim
2
Obwohl dies nicht sehr sexy ist, passt es in eine Zeile, ohne eine Kopie über ToList zu erstellen -foreach (var i in items) i.Dostuff();
James Westgate
1
Einige dieser Links sind tot! Was war der Artikel, der so viel hochgestimmt wurde!
Warren

Antworten:

863

Es gibt keine ForEach-Erweiterung für IEnumerable; nur für List<T>. Also könntest du es 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);
    }
}
Fredrik Kalseth
quelle
213
Seien Sie vorsichtig mit ToList (), da ToList () eine Kopie der Sequenz erstellt, was zu Leistungs- und Speicherproblemen führen kann.
Decasteljau
15
Es gibt nur wenige Gründe, warum ".Foreach ()" besser ist. 1. Multithreading, möglich durch PLINQ. 2. Ausnahmen: Möglicherweise möchten Sie alle Ausnahmen in der Schleife sammeln und sofort auslösen. und sie sind Rauschcodes.
Dennis C
12
PLINQ verwendet ForAll nicht ForEach, weshalb Sie ForEach nicht verwenden würden.
user7116
9
Sie sollten ein IENumerable<T>in der Erweiterungsmethode zurückgeben. Wie:public static IEnumerable<T> ForAll<T>(this IEnumerable<T> numeration, Action<T> action) { foreach (T item in enumeration) { action(item); } return enumeration; }
Kees C. Bakker
22
Beachten Sie, dass der Entwickler im ersten Fall 1) fast keine Eingabe spart und 2) unnötigerweise Speicher zuweist, um die gesamte Sequenz als Liste zu speichern, bevor er sie wegwirft. Denken Sie nach, bevor Sie LINQ
Mark Sowul
364

Fredrik hat das Update bereitgestellt, aber es kann sich lohnen, darüber nachzudenken, warum dies zunächst nicht im Framework enthalten ist. Ich glaube, die Idee ist, dass die LINQ-Abfrageoperatoren nebenwirkungsfrei sein und zu einer einigermaßen funktionalen Sichtweise auf die Welt passen sollten. ForEach ist eindeutig genau das Gegenteil - ein Konstrukt, das ausschließlich auf Nebenwirkungen basiert.

Das heißt nicht, dass dies eine schlechte Sache ist - nur über die philosophischen Gründe für die Entscheidung nachzudenken.

Jon Skeet
quelle
44
Und doch hat F # Seq.iter
Stephen Swensen
6
Es gibt jedoch eine sehr nützliche Sache, die LINQ-Funktionen normalerweise bieten, die foreach () nicht bietet - die anonyme Funktion, die von Linq-Funktionen verwendet wird, kann auch einen Index als Parameter verwenden, während Sie mit foreach den Klassiker für (int i .. ) konstruieren. Und funktionale Sprachen müssen eine Iteration zulassen, die Nebenwirkungen hat - sonst könnten Sie nie ein Jota Arbeit damit machen :) Ich möchte darauf hinweisen, dass die typische funktionale Methode darin besteht, die ursprüngliche Aufzählung unverändert zurückzugeben.
Walt W
3
@Walt: Ich bin mir immer noch nicht sicher, ob ich damit einverstanden bin, auch weil ein typischer wirklich funktionaler Ansatz nicht für jeden dieser Zwecke verwendet werden würde. Er würde einen funktionalen Ansatz verwenden, der eher LINQ ähnelt und eine neue Sequenz erstellt.
Jon Skeet
12
@ Stephen Swensen: Ja, F # hat Seq.iter, aber F # hat auch unveränderliche Datenstrukturen. Wenn Sie Seq.iter für diese verwenden, wird die ursprüngliche Seq nicht geändert. Eine neue Seq mit den Nebenwirkungen wird zurückgegeben.
Anders
7
@Anders - Seq.itergibt nichts zurück, geschweige denn eine neue Sequenz mit Nebenwirkungen. Es geht wirklich nur um Nebenwirkungen. Sie denken vielleicht daran Seq.map, weil Seq.iteres genau einer ForEachErweiterungsmethode auf entspricht IEnumerabe<_>.
Joel Mueller
39

Update 17.07.2012: Anscheinend wurde ab C # 5.0 das foreachunten beschriebene Verhalten geändert und " die Verwendung einer foreachIterationsvariablen 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 mit der Funktionsweise des Äquivalents "für das Verständnis" in anderen Sprachen übereinstimmt und mit der Art und Weise, wie ich es erwarten würde (persönliche Meinung hier nur, weil andere ihre erwähnt haben Meinung zur Lesbarkeit). Siehe alle Fragen zu " Zugriff auf modifizierten Abschluss " sowie " Schließen über die als schädlich geltende Schleifenvariable ". Dies ist nur "schädlich", da "foreach" in C # implementiert ist.

Nehmen Sie die folgenden Beispiele mit der funktional äquivalenten Erweiterungsmethode, die der Antwort von @Fredrik Kalseth entspricht.

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 benutze Observable nur, weil es nicht weit hergeholt ist, so etwas zu tun. Offensichtlich gibt es bessere Möglichkeiten, dieses Beobachtbare zu schaffen. Ich versuche nur, einen Punkt zu demonstrieren. Normalerweise wird der Code, der das Observable abonniert hat, asynchron und möglicherweise in einem anderen Thread ausgeführt. Bei Verwendung von "foreach" kann dies zu sehr seltsamen 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: 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()));
}
drstevens
quelle
Was sagt die Resharper-Warnung?
Reggaeguitar
1
@reggaeguitar Ich habe C # seit 7 oder 8 Jahren nicht mehr verwendet, seitdem C # 5.0 veröffentlicht wurde, was das hier beschriebene Verhalten geändert hat. Zu der Zeit gab es diese Warnung stackoverflow.com/questions/1688465/… . Ich bezweifle, dass es dennoch davor warnt.
Drstevens
34

Sie können die FirstOrDefault()Erweiterung verwenden, für die verfügbar ist IEnumerable<T>. Wenn Sie falsevom Prädikat zurückkehren, wird es für jedes Element ausgeführt, es ist jedoch egal, ob es tatsächlich eine Übereinstimmung findet. Dadurch wird der ToList()Overhead vermieden.

IEnumerable<Item> items = GetItems();
items.FirstOrDefault(i => { i.DoStuff(); return false; });
Rhames
quelle
11
Ich stimme auch zu. Es mag ein kluger kleiner Hack sein, aber auf den ersten Blick ist dieser Code nicht klar, was er tut, sicherlich im Vergleich zu einer Standard-foreach-Schleife.
Connell
26
Ich musste abstimmen, denn obwohl es technisch korrekt ist, werden Ihr zukünftiges Selbst und alle Ihre Nachfolger Sie für immer hassen, weil Sie stattdessen foreach(Item i in GetItems()) { i.DoStuff();}mehr Charaktere genommen und es extrem verwirrend gemacht haben
Mark Sowul
14
Ok, aber wahrscheinlich ein besser lesbarer Hack:items.All(i => { i.DoStuff(); return true; }
Nawfal
5
Ich weiß, dass dies ein ziemlich alter Beitrag ist, aber ich musste ihn auch ablehnen. Ich bin damit einverstanden, dass es ein kluger Trick ist, aber er kann nicht gewartet werden. Auf den ersten Blick sieht es so aus, als würde etwas nur mit dem ersten Element in der Liste geschehen. Dies könnte in Zukunft leicht zu weiteren Fehlern führen, da Codierer falsch verstehen, wie es funktioniert und was es tun soll.
Lee
4
Dies ist Nebenwirkungsprogrammierung und IMHO entmutigt.
Anish
21

Ich nahm Fredriks Methode und änderte den Rückgabetyp.

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

BEARBEITEN: Wenn dies nicht klar war, muss jede Verwendung dieser Methode mit ToList () oder einer anderen Methode enden , um die Methode zu zwingen, an der vollständigen Aufzählung zu arbeiten. Andernfalls würde 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 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, schlägt der Test fehl , da der StringBuilder eine leere Zeichenfolge enthält. Dies liegt daran, dass keine Methode ForEach zur Aufzählung gezwungen hat.

Dor Rotman
quelle
Ihre alternative Implementierung von ForEachist interessant, entspricht jedoch nicht dem Verhalten von List.ForEach, dessen Signatur lautet public void ForEach(Action<T> action). Es stimmt auch nicht mit dem Verhalten der Observable.ForEachErweiterung überein IObservable<T>, deren Signatur lautet public static void ForEach<TSource>(this IObservable<TSource> source, Action<TSource> onNext). FWIW, das ForEachÄquivalent zu Scala-Sammlungen, auch solche, die faul sind, haben ebenfalls einen Rückgabetyp, der für void in C # entspricht.
Drstevens
Dies ist die beste Antwort: verzögerte Ausführung + fließende API.
Alexander Danilov
3
Dies funktioniert genauso wie das Anrufen Selectmit ToList. Der Zweck von ForEachist, nicht anrufen zu müssen ToList. Es sollte sofort ausgeführt werden.
t3chb0t
3
Was wäre der Vorteil dieses "ForEach", dessen Ausführung verschoben wird, gegenüber einem, der sofort ausgeführt wird? Das Aufschieben (was für die Verwendung von ForEach unpraktisch ist) verbessert nicht die Tatsache, dass ForEach nur dann nützliche Arbeit leistet, wenn die Aktion Nebenwirkungen hat [die übliche Beschwerde, die gegen einen solchen Linq-Operator eingereicht wird]. Wie hilft diese Änderung? Darüber hinaus dient das hinzugefügte "ToList ()", um die Ausführung zu erzwingen, keinem nützlichen Zweck. Es ist ein Unfall, der darauf wartet, passiert zu werden. Der Punkt von "ForEach" sind seine Nebenwirkungen. Wenn Sie eine zurückgegebene Aufzählung wünschen, ist SELECT sinnvoller.
ToolmakerSteve
20

Halten Sie Ihre Nebenwirkungen von meiner IEnumerable fern

Ich möchte in LINQ das Gleiche tun, aber ich kann nicht herausfinden, wie:

Wie andere hier und im Ausland darauf hingewiesen haben, LINQ undIEnumerable , wird erwartet, dass Methoden sind.

Möchten Sie wirklich mit jedem Element in der IEnumerable "etwas tun"? Dann foreachist die beste Wahl. Die Leute sind nicht überrascht, wenn hier Nebenwirkungen auftreten.

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

Ich wette, Sie wollen keine Nebenwirkung

Nach meiner Erfahrung sind Nebenwirkungen jedoch normalerweise nicht erforderlich. Meistens wartet eine einfache LINQ-Abfrage darauf, entdeckt zu werden, zusammen mit einer Antwort von Jon Skeet, Eric Lippert oder Marc Gravell auf StackOverflow.com, in der erklärt wird, wie Sie das tun, was Sie wollen!

Einige Beispiele

Wenn Sie tatsächlich nur einen Wert aggregieren (akkumulieren), sollten Sie die AggregateErweiterungsmethode in Betracht ziehen .

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

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

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

Oder möchten Sie eine Nachschlagetabelle erstellen:

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

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

cdiggins
quelle
7
Ah, also ist Select Map und Aggregate Reduce.
Darrel Lee
17

Wenn Sie wie die Aufzählung handeln wollen Rollen sollten Sie liefern jedes Element.

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;
        }
    }
}
regisbsb
quelle
Dies ist nicht wirklich ein ForEach, wenn Sie den Artikel zurückgeben, sondern eher ein Tap.
EluciusFTW
10

Es gibt eine experimentelle Version von Microsoft für interaktive Erweiterungen von LINQ (weitere Informationen zu NuGet finden Sie im Profil von RxTeams für weitere Links). Das Channel 9-Video erklärt es gut.

Die Dokumente werden nur im XML-Format bereitgestellt. Ich habe diese Dokumentation in Sandcastle ausgeführt , damit sie in einem besser lesbaren Format vorliegt. Entpacken Sie das Dokumentarchiv und suchen Sie nach index.html .

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

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

numbers.ForEach(x => Console.WriteLine(x*x));
John Wigger
quelle
2
Der Arbeitslink zu Interactive Extensions: microsoft.com/download/en/details.aspx?id=27203
Maciek Świszczowski
8

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

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

um eine parallele foreach-Schleife auf einem IEnumerable durchzuführen.

Wolf5
quelle
Solange Ihre Aktion threadsicher ist, ist alles gut. Aber was passiert, wenn Sie eine nicht threadsichere Aktion ausführen müssen? es kann ein ziemliches Chaos verursachen ...
shirbr510
2
Wahr. Es ist nicht threadsicher, es werden verschiedene Threads aus einem Thread-Pool verwendet ... Und ich muss meine Antwort überarbeiten. Es gibt kein ForEach (), wenn ich es jetzt versuche. Muss mein Code gewesen sein, der eine Erweiterung mit ForEach enthält, als ich darüber nachdachte.
Wolf5
6

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 man ihn betrachtet.

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

Dies wird erst ausgeführt, wenn Sie eine "Zählung" oder eine "ToList ()" oder etwas darauf ausfü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 leistungsstark und effizient, aber Sie sollten lernen, ihre Natur zu schätzen. Und das nicht nur, um sie zu programmieren, um ein paar Zeichen zu speichern, die die träge Bewertung überschreiben.

Tormod
quelle
5

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

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

Kurz und einfach (st).

Nenad
quelle
Sie können die gesamte erste Zeile löschen und GetItems direkt in foreach
tymtam
3
Na sicher. Es ist der Klarheit halber so geschrieben. Damit ist klar, welche GetItems()Methode zurückgibt.
Nenad
4
Wenn ich jetzt meinen Kommentar lese, sehe ich, dass ich Ihnen etwas erzähle, das Sie nicht wissen. Das habe ich nicht so gemeint. Was ich meinte ist, dass foreach (var item in GetItems()) item.DoStuff();das nur eine Schönheit ist.
Tymtam
4

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 (Entschuldigung für die Schriftarten, das hat das System entschieden)

Paulustrious
quelle
4

Wie bereits zahlreiche Antworten zeigen, können Sie eine solche Erweiterungsmethode ganz einfach selbst hinzufügen. Wenn Sie dies jedoch nicht möchten, obwohl mir in der BCL so etwas nicht bekannt ist, gibt es im SystemNamespace immer noch eine Option , wenn Sie bereits einen Verweis auf Reactive Extension haben (und wenn Sie dies nicht tun) , du solltest 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.

Mark Seemann
quelle
Sieht so aus, als wäre das Paket jetzt veraltet
reggaeguitar
3

ForEach kann auch Chained , kurz nach der Aktion auf die pileline zurückstellen . 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
}

Edit: eine faule Version verwendet yield return, wie diese .

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, zum Beispiel ToList (), 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.

Rm558
quelle
2
Hast du das getestet? Es gibt verschiedene Zusammenhänge, in denen sich diese Methode kontraintuitiv verhält. Besser wäreforeach(T item in enu) {action(item); yield return item;}
Taemyr
2
Dies wird zu mehreren Aufzählungen führen
regisbsb
Interessant. Überlegen Sie, wann ich das oben Genannte für eine Folge von 3 Aktionen tun möchte, anstatt foreach (T item in enu) { action1(item); action2(item); action3(item); }? Eine einzelne explizite Schleife. Ich denke, wenn es wichtig war, alle Aktionen1 auszuführen, bevor mit den Aktionen2 begonnen wird.
ToolmakerSteve
@ Rm558 - Ihr Ansatz mit "Yield Return". Pro: Listet die ursprüngliche Sequenz nur einmal auf, funktioniert also korrekt mit einem größeren Bereich von Aufzählungen. Con: Wenn Sie am Ende ".ToArray ()" vergessen, passiert nichts. Obwohl der Fehler offensichtlich sein wird, nicht lange übersehen, also keine großen Kosten. Fühlt sich für mich nicht "sauber" an, ist aber der richtige Kompromiss, wenn man diesen Weg geht (ein ForEach-Betreiber).
ToolmakerSteve
1
Dieser Code ist aus transaktionaler Sicht nicht sicher. Was ist, wenn es fehlschlägt ProcessShipping()? Sie mailen dem Käufer, belasten seine Kreditkarte, schicken ihm aber nie seine Sachen? Auf jeden Fall nach Problemen fragen.
zmechanic
2

Diese "funktionale Herangehensweise" Abstraktion leckt große Zeit. Nichts auf der Sprachebene verhindert Nebenwirkungen. Solange Sie Ihr Lambda / Delegat für jedes Element im Container aufrufen können, erhalten Sie das Verhalten "ForEach".

Hier zum Beispiel eine Möglichkeit, srcDictionary mit destDictionary zusammenzuführen (falls 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();
Zar Shardan
quelle
6
Wenn Sie diesen Haftungsausschluss hinzufügen müssen, sollten Sie ihn wahrscheinlich nicht veröffentlichen. Nur meine Meinung.
Andrew Grothe
2
Wie zum Teufel ist das besser als foreach(var tuple in srcDictionary) { destDictionary[tuple.Key] = tuple.Value; }? Wenn nicht im Produktionscode, wo und warum sollten Sie dies jemals verwenden?
Mark Sowul
3
Dies nur um zu zeigen, dass es grundsätzlich möglich ist. Es tut auch das, was OP verlangt, und ich habe klar darauf hingewiesen, dass es nicht in der Produktion verwendet werden sollte, immer noch neugierig ist und pädagogischen Wert hat.
Zar Shardan
2

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

Verlängerungsmethode:

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);
    }
Scott Nimrod
quelle
1

Ich bin mit der Vorstellung nicht einverstanden, dass Linkerweiterungsmethoden nebenwirkungsfrei sein sollten (nicht nur, weil dies nicht der Fall ist, sondern jeder Delegierte Nebenwirkungen erzielen kann).

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 eigentlich nur eine Art Spätbindung, mit der man eine von vielen möglichen Aktionen aufrufen kann, die Nebenwirkungen auf eine Folge von Elementen haben, 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.

Caddzooks
quelle
9
Es gibt einen Unterschied zwischen der Frage, ob eine Erweiterungsmethode Nebenwirkungen haben kann und ob sie auftreten sollte. Sie weisen nur darauf hin, dass dies möglich ist, obwohl es sehr gute Gründe geben kann, vorzuschlagen, dass sie keine Nebenwirkungen haben sollten. In der funktionalen Programmierung sind alle Funktionen nebenwirkungsfrei, und wenn Sie funktionale Programmierkonstrukte verwenden, möchten Sie möglicherweise davon ausgehen, dass dies der Fall ist.
Dave Van den Eynde
1

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

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

Für VB.NET sollten Sie Folgendes verwenden:

listVariable.ForEach(Sub(i) i.Property = "Value")
Israel Margulies
quelle
HINWEIS: Dies wird nur kompiliert, wenn Sie ein Listoder haben IList. IEnumerableSchreiben Sie im Allgemeinen das VB-Äquivalent der ForEach-Erweiterungsmethode der akzeptierten Antwort .
ToolmakerSteve
-1

Noch ein ForEachBeispiel

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;
}
Neil Martin
quelle
4
Was für eine Verschwendung von Erinnerung. Dadurch wird ToList aufgerufen, um eine Liste hinzuzufügen. Warum gibt dies nicht nur Adressen zurück. Wählen Sie (a => MapToDomain (a))?
Mark Sowul