LINQ-equivalent van foreach voor IEnumerable<T>

Ik zou het equivalent van het volgende willen doen in LINQ, maar ik weet niet hoe:

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

Wat is de echte syntaxis?


Antwoord 1, autoriteit 100%

Er is geen ForEach-extensie voor IEnumerable; alleen voor List<T>. Dus je zou kunnen doen

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

U kunt ook uw eigen ForEach-extensiemethode schrijven:

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

Antwoord 2, autoriteit 41%

Fredrik heeft de oplossing geleverd, maar het is misschien de moeite waard om te overwegen waarom dit om te beginnen niet in het raamwerk zit. Ik geloof dat het idee is dat de LINQ-queryoperators vrij van neveneffecten moeten zijn, passend bij een redelijk functionele manier om naar de wereld te kijken. Het is duidelijk dat ForEach precies het tegenovergestelde is – een puur op neveneffecten gebaseerde constructie.

Dat wil niet zeggen dat dit een slechte zaak is om te doen – gewoon nadenken over de filosofische redenen achter de beslissing.


Antwoord 3, autoriteit 4%

Update 17-7-2012: vanaf C# 5.0 is het gedrag van ForEach dat hieronder wordt beschreven blijkbaar gewijzigd en “het gebruik van een ForEach iteratievariabele in een geneste lambda-expressie nee langer levert onverwachte resultaten op.” Dit antwoord is niet van toepassing op C# ? 5.0.

@John Skeet en iedereen die de voorkeur geeft aan het foreach-zoekwoord.

Het probleem met “foreach” in C# vóór 5.0, is dat het inconsistent is met hoe het equivalent “voor begrip” in andere talen werkt, en met hoe ik zou verwachten dat het zou werken (persoonlijke mening hier alleen vermeld omdat anderen hun mening over de leesbaarheid hebben vermeld). Bekijk alle vragen over “Toegang tot gewijzigde afsluiting
evenals “Het sluiten van de lusvariabele die als schadelijk wordt beschouwd“. Dit is alleen “schadelijk” vanwege de manier waarop “foreach” is geïmplementeerd in C#.

Neem de volgende voorbeelden met behulp van de functioneel equivalente uitbreidingsmethode als die in het antwoord van @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);
        }
    }
}

Excuses voor het overdreven gekunstelde voorbeeld. Ik gebruik alleen Observable omdat het niet helemaal vergezocht is om zoiets te doen. Het is duidelijk dat er betere manieren zijn om dit waarneembare te creëren, ik probeer alleen een punt aan te tonen. Meestal wordt de code die is geabonneerd op het waarneembare, asynchroon en mogelijk in een andere thread uitgevoerd. Als u “foreach” gebruikt, kan dit zeer vreemde en mogelijk niet-deterministische resultaten opleveren.

De volgende test met de “ForEach”-extensiemethode slaagt:

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

Het volgende mislukt met de fout:

Verwacht: gelijk aan < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 >
Maar was: < 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()));
}

Antwoord 4, autoriteit 4%

U kunt de extensie FirstOrDefault() gebruiken, die beschikbaar is voor IEnumerable<T>. Door false terug te geven vanuit het predikaat, wordt het uitgevoerd voor elk element, maar het maakt niet uit dat het geen overeenkomst vindt. Dit voorkomt de overhead van ToList().

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

Antwoord 5, autoriteit 2%

Ik heb de methode van Fredrik gebruikt en het retourtype aangepast.

Op deze manier ondersteunt de methode uitgestelde uitvoering net als andere LINQ-methoden.

BEWERK: Als dit niet duidelijk was, moet elk gebruik van deze methode moeten eindigen met ToList() of een andere manier om de methode te forceren om aan de volledige opsombaar. Anders zou de actie niet worden uitgevoerd!

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

En hier is de test om het te helpen zien:

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

Als je de ToList() uiteindelijk verwijdert, zul je zien dat de test mislukt omdat de StringBuilder een lege string bevat. Dit komt omdat geen enkele methode de ForEach dwong om op te sommen.


Antwoord 6, autoriteit 2%

Houd uw bijwerkingen uit mijn IEnumerable

Ik zou het equivalent van het volgende willen doen in LINQ, maar ik weet niet hoe:

Zoals anderen hebben opgemerkt hier en in het buitenland zullen LINQ- en IEnumerable-methoden naar verwachting bijwerking vrij.

Wilt u echt “iets doen” met elk item in het IEnumerable? Dan is ForEach de beste keuze. Mensen zijn niet verbaasd als hier bijwerkingen optreden.

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

Ik wed dat je geen bijwerking wilt

Naar mijn ervaring zijn bijwerkingen echter meestal niet nodig. Vaker wel dan niet is er een eenvoudige LINQ-query die wacht om ontdekt te worden, vergezeld van een StackOverflow.com-antwoord van Jon Skeet, Eric Lippert of Marc Gravell waarin wordt uitgelegd hoe u kunt doen wat u wilt!

Enkele voorbeelden

Als je eigenlijk alleen maar wat waarde verzamelt (accumuleert), moet je de Aggregate-extensiemethode overwegen.

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

Misschien wil je een nieuwe IEnumerable maken van de bestaande waarden.

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

Of misschien wilt u een opzoektabel maken:

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

De lijst (niet helemaal bedoelde woordspeling) met mogelijkheden gaat maar door.


Antwoord 7, autoriteit 2%

Als u wilt optreden als de opsommingsrollen, moet u elk item opgeven.

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

Antwoord 8

Er is een experimentele release door Microsoft van Interactive Extensions to LINQ (ook op NuGet, zie RxTeams’s profiel voor meer links). De Channel 9-video legt het goed uit .

De documenten worden alleen in XML-indeling geleverd. Ik heb deze documentatie in Sandcastle uitgevoerd om het in een beter leesbaar formaat te krijgen. Pak het documentenarchief uit en zoek naar index.html.

Onder vele andere voordelen biedt het de verwachte ForEach-implementatie. Hiermee kunt u code als volgt schrijven:

int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8 };
numbers.ForEach(x => Console.WriteLine(x*x));

Antwoord 9

Zoveel antwoorden, maar ALLEMAAL slagen er niet in om één zeer belangrijk probleem aan te wijzen met een aangepaste algemene ForEach extensie: Prestaties! En meer specifiek, geheugengebruik en GC.

Bekijk het onderstaande voorbeeld. Targeting op .NET Framework 4.7.2 of .NET Core 3.1.401, configuratie is Release en platform is 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);
        }
    }
}

Op het eerste gezicht zouden alle drie de varianten even goed moeten presteren. Echter, wanneer de ForEach extensie methode vele, vele keren wordt aangeroepen, zal je eindigen met rommel die een kostbare GC impliceert. Het is zelfs bewezen dat het hebben van deze ForEach-extensiemethode op een hot pad de prestaties in onze loop-intensieve applicatie volledig de das om doet.

Op dezelfde manier zal de wekelijks getypte ForEach-lus ook rommel produceren, maar het zal nog steeds sneller en minder geheugenintensief zijn dan de ForEach-extensie (die ook last heeft van een toewijzing delegeren).

Sterk getypt foreach: geheugengebruik

Geen toewijzingen. Geen GC

Wekelijks getypte foreach: geheugengebruik

voer hier de afbeeldingsbeschrijving in

Voor elke extensie: geheugengebruik

Veel toewijzingen. Zware GC.

Analyse

Voor een sterk getypte ForEach kan de compiler elke geoptimaliseerde enumerator (bijvoorbeeld op waarde gebaseerd) van een klasse gebruiken, terwijl een generieke ForEach-extensie moet terugvallen op een generieke enumerator die bij elke run wordt toegewezen. Bovendien zal de daadwerkelijke afgevaardigde ook een extra toewijzing inhouden.

Je zou vergelijkbare slechte resultaten krijgen met de WithoutLinqNoGood methode. Daar is het argument van het type IEnumerable<int> in plaats van List<int>, wat hetzelfde type enumeratortoewijzing impliceert.

Hieronder staan ​​de relevante verschillen in IL. Een op waarden gebaseerde enumerator heeft zeker de voorkeur!

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

vs

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

Conclusie

De OP vroeg hoe je ForEach() aanroept op een IEnumerable<T>. Het oorspronkelijke antwoord laat duidelijk zien hoe het kan. Natuurlijk kun je het, maar nogmaals; mijn antwoord laat duidelijk zien dat je dat niet zou moeten doen.

Hetzelfde gedrag geverifieerd bij het targeten van .NET Core 3.1.401 (compileren met Visual Studio 16.7.2).


Antwoord 10

Volgens PLINQ (beschikbaar sinds .Net 4.0), kunt u een

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

om een ​​parallelle foreach-lus te maken op een IEnumerable.


Antwoord 11

Het doel van ForEach is om bijwerkingen te veroorzaken.
IEnumerable is voor luie opsomming van een set.

Dit conceptuele verschil is goed zichtbaar als je erover nadenkt.

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

Dit wordt pas uitgevoerd als u een “count” of een “ToList()” of iets dergelijks doet.
Het is duidelijk niet wat wordt uitgedrukt.

U moet de IEnumerable-extensies gebruiken voor het opzetten van iteratieketens, waarbij inhoud wordt gedefinieerd aan de hand van hun respectievelijke bronnen en voorwaarden. Expressiebomen zijn krachtig en efficiënt, maar u moet hun aard leren waarderen. En niet alleen voor het programmeren om hen heen om een ​​paar karakters op te slaan die de luie evaluatie overheersen.


Antwoord 12

Veel mensen noemden het, maar ik moest het opschrijven. Is dit niet het meest duidelijk/leesbaar?

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

Kort en simpel(st).


Antwoord 13

Nu hebben we de mogelijkheid om…

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

Natuurlijk opent dit een heel nieuw blik wormen.

ps (Sorry voor de lettertypen, dat heeft het systeem besloten)


Antwoord 14

Zoals talloze antwoorden al aangeven, kun je zo’n extensiemethode eenvoudig zelf toevoegen. Als u dat echter niet wilt doen, hoewel ik niet op de hoogte ben van iets dergelijks in de BCL, is er nog steeds een optie in de naamruimte System, als u al een verwijzing naar Reactive Extension (en als je dat niet doet, zou je dat moeten hebben):

using System.Reactive.Linq;
items.ToObservable().Subscribe(i => i.DoStuff());

Hoewel de namen van de methoden een beetje anders zijn, is het eindresultaat precies wat u zoekt.


Antwoord 15

ForEach kan ook geketend zijn, gewoon terugplaatsen op de stapellijn na de actie. vloeiend blijven


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

Een Eager versie van implementatie.

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
}

Bewerken: een Luie versie gebruikt rendementsrendement, zoals dit.

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

De Lazy-versie moet worden gerealiseerd, ToList() bijvoorbeeld, anders gebeurt er niets. zie hieronder de geweldige opmerkingen van ToolmakerSteve.

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

Ik bewaar zowel ForEach() als ForEachLazy() in mijn bibliotheek.


Antwoord 16

Geïnspireerd door Jon Skeet, heb ik zijn oplossing uitgebreid met het volgende:

Uitbreidingsmethode:

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

Klant:

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

Antwoord 17

Deze abstractie van ‘functionele benadering’ lekt enorm. Niets op taalniveau voorkomt bijwerkingen. Zolang je het je lambda/delegate voor elk element in de container kunt laten aanroepen, krijg je het “ForEach”-gedrag.

Hier bijvoorbeeld een manier om srcDictionary samen te voegen met destDictionary (als de sleutel al bestaat – overschrijft)

dit is een hack en mag in geen enkele productiecode worden gebruikt.

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

Antwoord 18

MoreLinq heeft IEnumerable<T>.ForEach en een heleboel andere handige extensies. Het is waarschijnlijk niet de moeite waard om de afhankelijkheid alleen voor ForEach te nemen, maar er staan ​​veel nuttige dingen in.

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

https://github.com/morelinq/MoreLINQ


Antwoord 19

Ik ben het oneens met het idee dat methoden voor linkextensie vrij van bijwerkingen moeten zijn (niet alleen omdat ze dat niet zijn, elke afgevaardigde kan bijwerkingen veroorzaken).

Denk aan het volgende:

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

Wat het voorbeeld laat zien, is eigenlijk gewoon een soort late binding waarmee men een van de vele mogelijke acties met neveneffecten op een reeks elementen kan aanroepen, zonder een grote schakelaarconstructie te hoeven schrijven om de waarde te decoderen die de actie en vertaal het naar de bijbehorende methode.


Antwoord 20

Om vloeiend te blijven kun je zo’n truc gebruiken:

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

Antwoord 21

Voor VB.NET moet je gebruiken:

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

Antwoord 22

Nog een ForEach Voorbeeld

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

LEAVE A REPLY

Please enter your comment!
Please enter your name here

one × 1 =

Other episodes