Domanda

Vorrei fare l'equivalente di quanto segue in LINQ, ma non riesco a capire come:

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

Qual è la sintassi reale?

È stato utile?

Soluzione

Non esiste un'estensione ForEach per IEnumerable ; solo per Elenco < T > . Quindi potresti farlo

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

In alternativa, scrivi il tuo metodo di estensione ForEach:

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

Altri suggerimenti

Fredrik ha fornito la correzione, ma potrebbe valere la pena di considerare perché questo non è nel framework per cominciare. Credo che l'idea sia che gli operatori di query LINQ dovrebbero essere privi di effetti collaterali, adattandosi a un modo ragionevolmente funzionale di guardare il mondo. Chiaramente ForEach è esattamente l'opposto: un costrutto puramente basato sugli effetti collaterali.

Questo non vuol dire che questa è una brutta cosa da fare - solo pensare alle ragioni filosofiche dietro la decisione.

Aggiorna 17/07/2012: apparentemente a partire da C # 5.0, il comportamento di foreach descritto di seguito è stato modificato e " l'uso di una variabile di iterazione foreach in un'espressione lambda nidificata non produce più risultati imprevisti. " Questa risposta non si applica a C # = 5.0.

@John Skeet e tutti coloro che preferiscono la parola chiave foreach.

Il problema con " foreach " in C # prima della 5.0 , è incompatibile con il modo in cui l'equivalente "per comprensione" funziona in altre lingue e con come mi aspetto che funzioni (opinione personale dichiarata qui solo perché altri hanno menzionato la loro opinione in merito alla leggibilità). Consulta tutte le domande relative a " Accesso alla chiusura modificata " oltre a " Chiudere la variabile loop considerata dannosa " ;. Questo è solo "dannoso" a causa del modo in cui "foreach" è implementato in C #.

Prendi i seguenti esempi usando il metodo di estensione funzionalmente equivalente a quello nella risposta di @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);
        }
    }
}

Ci scusiamo per l'esempio eccessivamente inventato. Sto solo usando Observable perché non è del tutto inverosimile fare qualcosa del genere. Ovviamente ci sono modi migliori per creare questo osservabile, sto solo cercando di dimostrare un punto. In genere il codice sottoscritto all'osservabile viene eseguito in modo asincrono e potenzialmente in un altro thread. Se si utilizza " foreach " ;, ciò potrebbe produrre risultati molto strani e potenzialmente non deterministici.

Il seguente test usando " ForEach " passa il metodo di estensione:

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

L'errore seguente non riesce:

Previsto: equivalente a < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 >    Ma era: < 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()));
}

Puoi utilizzare l'estensione FirstOrDefault () , disponibile per IEnumerable < T > . Restituendo false dal predicato, verrà eseguito per ogni elemento ma non gli importerà che non trovi effettivamente una corrispondenza. Ciò eviterà l'overhead ToList () .

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

Ho preso il metodo di Fredrik e modificato il tipo restituito.

In questo modo, il metodo supporta esecuzione differita come altri metodi LINQ.

MODIFICA: Se ciò non fosse chiaro, qualsiasi utilizzo di questo metodo deve terminare con ToList () o in qualsiasi altro modo per forzare il metodo a lavorare sul completo enumerabile. Altrimenti, l'azione non verrebbe eseguita!

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

Ed ecco il test per aiutarti a vederlo:

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

Se rimuovi la ToList () alla fine, vedrai il test fallire poiché StringBuilder contiene una stringa vuota. Questo perché nessun metodo ha forzato l'enumerazione di ForEach.

Tieni i tuoi effetti collaterali fuori dal mio IEnumerable

  
    

Vorrei fare l'equivalente di quanto segue in LINQ, ma non riesco a capire come:

  

Come altri hanno sottolineato qui e all'estero ci si aspetta che i metodi LINQ e IEnumerable siano senza effetti collaterali.

Vuoi davvero " fare qualcosa " a ciascun elemento in IEnumerable? Quindi foreach è la scelta migliore. Le persone non sono sorprese quando si verificano effetti collaterali qui.

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

Scommetto che non vuoi un effetto collaterale

Tuttavia, nella mia esperienza, gli effetti collaterali di solito non sono richiesti. Il più delle volte c'è una semplice query LINQ in attesa di essere scoperta accompagnata da una risposta StackOverflow.com di Jon Skeet, Eric Lippert o Marc Gravell che spiega come fare ciò che vuoi!

Alcuni esempi

Se in realtà stai solo aggregando (accumulando) del valore, dovresti considerare il metodo di estensione Aggregate .

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

Forse vuoi creare un nuovo IEnumerable dai valori esistenti.

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

O forse vuoi creare una tabella di ricerca:

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

L'elenco (gioco di parole non interamente inteso) di possibilità potrebbe continuare all'infinito.

Se si desidera agire come i rotoli di enumerazione, è necessario cedere ciascun articolo.

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

Esiste una versione sperimentale di Microsoft di Estensioni interattive per LINQ (anche su NuGet , vedi profilo di RxTeams per altri link). Il Video del canale 9 lo spiega bene .

I suoi documenti sono forniti solo in formato XML. Ho eseguito questa documentazione in Sandcastle per consentirle di essere in un formato più leggibile. Decomprimi l'archivio documenti e cerca index.html .

Tra le tante altre chicche, fornisce l'implementazione prevista di ForEach. Ti permette di scrivere codice in questo modo:

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

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

Secondo PLINQ (disponibile da .Net 4.0), puoi fare un

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

per eseguire un ciclo foreach parallelo su un IEnumerable.

Lo scopo di ForEach è quello di causare effetti collaterali. IEnumerable è per l'enumerazione lazy di un set.

Questa differenza concettuale è ben visibile se la si considera.

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

Questo non verrà eseguito fino a quando non eseguirai un "conteggio" o un " ToList () " o qualcosa del genere. Chiaramente non è ciò che viene espresso.

Dovresti usare le estensioni IEnumerable per impostare catene di iterazioni, definendo il contenuto in base alle rispettive fonti e condizioni. Gli alberi delle espressioni sono potenti ed efficienti, ma dovresti imparare ad apprezzare la loro natura. E non solo per programmare intorno a loro per salvare alcuni personaggi che prevalgono sulla valutazione pigra.

Molte persone lo hanno menzionato, ma ho dovuto scriverlo. Non è questo il più chiaro / più leggibile?

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

Breve e semplice (st).

Come già indicato da numerose risposte, puoi facilmente aggiungere tu stesso un metodo di estensione. Tuttavia, se non vuoi farlo, anche se non sono a conoscenza di nulla del genere nel BCL, c'è ancora un'opzione nello spazio dei nomi System , se hai già un riferimento a < a href = "https://www.nuget.org/packages/Rx-Main" rel = "nofollow noreferrer"> Estensione reattiva (e in caso contrario, dovresti avere):

using System.Reactive.Linq;

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

Sebbene i nomi dei metodi siano leggermente diversi, il risultato finale è esattamente quello che stai cercando.

Ora abbiamo la possibilità di ...

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

Ovviamente, questo apre una nuova lattina di ossiuri.

ps (mi dispiace per i caratteri, è quello che il sistema ha deciso)

Questo "approccio funzionale" l'astrazione perde alla grande. Niente a livello linguistico impedisce effetti collaterali. Fintanto che puoi farlo chiamare il tuo lambda / delegato per ogni elemento nel contenitore - otterrai il "ForEach" comportamento.

Qui ad esempio un modo per unire srcDictionary in destDictionary (se la chiave esiste già - sovrascrive)

questo è un trucco e non dovrebbe essere usato in nessun codice di produzione.

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

Ispirato da Jon Skeet, ho esteso la sua soluzione con quanto segue:

Metodo di estensione:

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

Cliente:

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

ForOach può anche essere Incatenato , rimesso sulla linea guida dopo l'azione. resta fluente


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

Una versione desiderosa di implementazione.

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
}

Modifica: una versione Lazy sta utilizzando il rendimento, come questo .

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

La versione Lazy BISOGNI da materializzare, ToList () per esempio, altrimenti non succede nulla. vedi sotto grandi commenti da ToolmakerSteve.

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

Conservo sia ForEach () che ForEachLazy () nella mia libreria.

Non sono d'accordo con l'idea che i metodi di estensione dei link debbano essere privi di effetti collaterali (non solo perché non lo sono, qualsiasi delegato può eseguire effetti collaterali).

Considera quanto segue:

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

Ciò che mostra l'esempio è in realtà solo una sorta di late-binding che consente di invocare una delle tante azioni possibili che hanno effetti collaterali su una sequenza di elementi, senza dover scrivere un costrutto switch grande per decodificare il valore che definisce il agire e tradurlo nel suo metodo corrispondente.

Per VB.NET dovresti usare:

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

Per rimanere fluenti si può usare un simile trucco:

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

MoreLinq ha IEnumerable < T > .ForEach e un sacco di altre estensioni utili. Probabilmente non vale la pena prendere la dipendenza solo per ForEach , ma ci sono molte cose utili lì dentro

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

https://github.com/morelinq/MoreLINQ

Ancora un altro ForEach Esempio

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;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top