Equivalente LINQ di foreach per IEnumerable

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

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

Qual è la vera syntax?

Non esiste un’estensione ForEach per IEnumerable ; solo per List . Quindi potresti farlo

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

In alternativa, scrivi il tuo metodo di estensione ForEach:

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

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.

Aggiornamento 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 annidata 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 # precedente alla 5.0 , è che non è coerente con il modo in cui l’equivalente “per la comprensione” funziona in altre lingue e con come mi aspetterei che funzioni (l’opinione personale è stata affermata qui solo perché altri hanno menzionato il loro parere sulla leggibilità). Vedi tutte le domande riguardanti ” Accesso alla chiusura modificata ” e ” Closing over the loop variable considerate nocive “. 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(this IEnumerable @this, Action action) { foreach (T item in @this) { action(item); } } } 

Ci scusiamo per l’esempio eccessivamente forzato. Sto solo usando Observable perché non è del tutto inverosimile fare qualcosa del genere. Ovviamente ci sono modi migliori per creare questo osservabile, sto solo tentando di dimostrare un punto. In genere il codice sottoscritto per l’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 che utilizza il metodo di estensione “ForEach” passa:

 [Test] public void ForEachExtensionWin() { //Yes, I know there is an Observable.Range. var values = Enumerable.Range(0, 10); var observable = Observable.Create>(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())); } 

Quanto segue non riesce con l’errore:

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

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

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

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 ForEach(this IEnumerable enumeration, Action action) { foreach (T item in enumeration) { action(item); yield return item; } } 

Ed ecco il test per aiutarti a vederlo:

 [Test] public void TestDefferedExecutionOfIEnumerableForEach() { IEnumerable 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.

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 qui e all’estero, i metodi LINQ e IEnumerable dovrebbero essere privi di effetti collaterali.

Vuoi davvero “fare qualcosa” per ogni object 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.

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

Se vuoi agire come i rotoli di enumerazione dovresti cedere ogni object.

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

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

 IEnumerable.AsParallel().ForAll() 

fare un ciclo foreach parallelo su un object IEnumerable.

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 express.

È 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.

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.

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

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

Breve e semplice (st).

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)

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

Metodo di estensione:

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

Cliente:

 var jobs = new List() { 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); } 

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> actions = new Dictionary>(); 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 elements ) { Action 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.

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

Per VB.NET dovresti usare:

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

ForEach può anche essere incatenato , basta rimetterlo sul pileline dopo l’azione. rimanere 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();}); 

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

Modifica 2 sopra il codice funziona, ma una versione migliore sta usando questo .

La modifica sotto era un esempio sbagliato, indicato da Taemyr. Grazie mille.

 Employees.ForEach(e=>e.Salary = e.Salary * 2) .Where (e=> e.Salary > 10000) .Average(e=> e.Salary); 

Ancora un altro esempio

 public static IList MapToDomain(IList addresses) { var workingAddresses = new List(); addresses.Select(a => a).ToList().ForEach(a => workingAddresses.Add(AddressModelMapper.MapToDomain(a))); return workingAddresses; } 

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.