what - Equivalente LINQ di foreach per IEnumerable<T>




ienumerable of string (14)

Mantieni i tuoi effetti collaterali fuori dal mio IEnumerable

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

Come altri hanno sottolineato here e all'estero, i metodi LINQ e IEnumerable dovrebbero essere privi di effetti collaterali.

Vuoi davvero "fare qualcosa" per ogni oggetto in IEnumerable? Quindi foreach è la scelta migliore. Le persone non sono sorprese quando gli effetti collaterali accadono qui.

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

Scommetto che non vuoi un effetto collaterale

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

Qualche esempio

Se in realtà si sta solo aggregando (accumulando) un certo valore, è necessario 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))

La lista (il gioco di parole non interamente inteso) delle possibilità va avanti e avanti.

Mi piacerebbe 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 vera sintassi?


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

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

C'è una versione sperimentale di Microsoft di Interactive Extensions per LINQ (anche su NuGet , vedi il profilo di RxTeams per altri link). Il video di Channel 9 lo spiega bene.

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

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

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

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

Come già indicato in numerose risposte, puoi facilmente aggiungere un metodo di estensione tu stesso. Tuttavia, se non vuoi farlo, anche se non sono a conoscenza di qualcosa di simile nel BCL, c'è ancora un'opzione nel namespace System , se hai già un riferimento a Reactive Extension (e se non lo fai) t, 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.


Fredrik ha fornito la soluzione, ma potrebbe valere la pena considerare perché non è nel framework per iniziare. 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 al mondo. Chiaramente ForEach è esattamente l'opposto - un costrutto basato esclusivamente sugli effetti collaterali.

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


Ho preso il metodo di Fredrik e ho modificato il tipo di ritorno.

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

EDIT: Se questo non era chiaro, qualsiasi utilizzo di questo metodo deve terminare con ToList () o qualsiasi altro modo per forzare il metodo a lavorare sull'enumerabile completo. 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 si rimuove la ToList () alla fine, il test fallirà poiché StringBuilder contiene una stringa vuota. Questo perché nessun metodo ha forzato l'enumerazione di ForEach.


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

Questa differenza concettuale è abbastanza visibile quando la consideri.

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

Questo non verrà eseguito finché non eseguirai un "conteggio" o una "Lista ()" o qualcosa del genere. Chiaramente non è ciò che viene espresso.

È necessario utilizzare le estensioni IEnumerable per impostare le catene di iterazione, definendo il contenuto in base alle rispettive origini e condizioni. Gli alberi di espressione sono potenti ed efficienti, ma dovresti imparare ad apprezzare la loro natura. E non solo per programmare attorno a loro per salvare alcuni personaggi che annullano la valutazione pigra.


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

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

Breve e semplice (st).


Non esiste un'estensione ForEach per IEnumerable ; solo per List<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);
    }
}

Ora abbiamo l'opzione 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 lattina completamente nuova di threadworms.

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


Questa astrazione "approccio funzionale" perde tempo. Nulla a livello di linguaggio previene gli effetti collaterali. Finché puoi farlo chiamare il tuo lambda / delegato per ogni elemento nel contenitore, otterrai il comportamento "Per ogni altro".

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

questo è un hack e non dovrebbe essere usato in alcun codice di produzione.

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

Se lo fai, ad esempio perché hai bisogno dell'indice nella tua iterazione, puoi sempre usare un costrutto Where:

linqObject.Where((obj, index) => {
  DoWork(obj, index);
  return true;
}).ToArray(); //MUST CALL ToArray() or ToList() or something to execute the lazy query, or the loop won't actually execute

Questo ha l'ulteriore vantaggio che l'array originale viene restituito "invariato" (gli oggetti a cui fa riferimento la lista sono gli stessi, sebbene potrebbero non avere gli stessi dati), che è spesso auspicabile in metodologie di programmazione funzionale / catena come LINQ.


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

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

fare un ciclo foreach parallelo su un oggetto IEnumerable.


Sono rispettosamente in disaccordo con la nozione secondo cui i metodi di estensione dei collegamenti dovrebbero 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 l'esempio mostra è in realtà solo una sorta di rilegatura tardiva che consente di invocare una delle molte azioni possibili con effetti collaterali su una sequenza di elementi, senza dover scrivere un grande costrutto switch per decodificare il valore che definisce l'azione e tradurre nel suo metodo corrispondente.





ienumerable