Domanda

Ispirato da un'altra domanda sui dispersi Zip funzione:

Perché non c'è ForEach metodo di estensione nel file Enumerable classe?O da qualche parte?L'unica classe che ottiene a ForEach il metodo è List<>.C'è un motivo per cui manca (prestazioni)?

È stato utile?

Soluzione

Nella lingua è già inclusa un'istruzione foreach che svolge il lavoro nella maggior parte dei casi.

Mi dispiacerebbe vedere quanto segue:

list.ForEach( item =>
{
    item.DoSomething();
} );

Invece di:

foreach(Item item in list)
{
     item.DoSomething();
}

Quest'ultimo è più chiaro e più facile da leggere nella maggior parte delle situazioni, anche se forse è un po' più lungo da scrivere.

Tuttavia, devo ammettere che ho cambiato posizione su questo tema;un metodo di estensione ForEach() sarebbe effettivamente utile in alcune situazioni.

Ecco le principali differenze tra la dichiarazione e il metodo:

  • Controllo del tipo:foreach viene eseguito in fase di runtime, ForEach() è in fase di compilazione (Big Plus!)
  • La sintassi per chiamare un delegato è infatti molto più semplice:oggetti.ForEach(FaiSomething);
  • ForEach() potrebbe essere concatenato:sebbene la malvagità/utilità di tale funzionalità sia aperta alla discussione.

Questi sono tutti ottimi punti sollevati da molte persone qui e posso capire perché alle persone manca la funzione.Non mi dispiacerebbe che Microsoft aggiungesse un metodo ForEach standard nella prossima iterazione del framework.

Altri suggerimenti

Il metodo ForEach è stato aggiunto prima di LINQ.Se aggiungi l'estensione ForEach, non verrà mai chiamata per le istanze List a causa dei vincoli dei metodi di estensione.Penso che il motivo per cui non è stato aggiunto sia per non interferire con quello esistente.

Tuttavia, se davvero ti manca questa piccola funzione, puoi lanciare la tua versione

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

Potresti scrivere questo metodo di estensione:

// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
    foreach (var e in source)
    {
        action(e);
        yield return e;
    }
}

Professionisti

Consente il concatenamento:

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);

Contro

In realtà non farà nulla finché non farai qualcosa per forzare l'iterazione.Per questo motivo non dovrebbe essere chiamato .ForEach().Potresti scrivere .ToList() alla fine, oppure potresti scrivere anche questo metodo di estensione:

// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
    foreach (var e in source)
    {
        // do nothing
        ;
    }

    return source;
}

Potrebbe trattarsi di un allontanamento troppo significativo dalle librerie C# di spedizione;i lettori che non hanno familiarità con i tuoi metodi di estensione non sapranno cosa pensare del tuo codice.

La discussione qui dà la risposta:

In realtà, la discussione specifica a cui ho assistito era incentrata sulla purezza funzionale.In un'espressione, spesso si presuppone che non si verifichino effetti collaterali.Avere ForEach invita specificamente agli effetti collaterali piuttosto che semplicemente sopportarli.-- Keith Farmer (Socio)

Fondamentalmente si è deciso di mantenere i metodi di estensione funzionalmente "puri".A ForEach incoraggerebbe gli effetti collaterali quando si utilizzano i metodi di estensione Enumerable, che non era l'intento.

Anche se sono d'accordo che è meglio usare il built-in foreach nella maggior parte dei casi, trovo che l'uso di questa variazione sull'estensione ForEach<> sia un po' più gradevole che dover gestire l'indice in un normale foreach me stessa:

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
    if (action == null) throw new ArgumentNullException("action");

    var index = 0;

    foreach (var elem in list)
        action(index++, elem);

    return index;
}
Esempio
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));

Ti darei:

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry

Una soluzione alternativa è scrivere .ToList().ForEach(x => ...).

professionisti

Facile da capire: il lettore deve solo sapere cosa viene fornito con C#, non eventuali metodi di estensione aggiuntivi.

Il rumore sintattico è molto lieve (aggiunge solo un po' di codice estraneo).

Di solito non costa memoria aggiuntiva, poiché è nativo .ForEach() dovrebbe comunque realizzare l'intera collezione.

contro

L'ordine delle operazioni non è l'ideale.Preferisco realizzare un elemento, poi agire su di esso e poi ripetere.Questo codice realizza prima tutti gli elementi, poi agisce su ciascuno di essi in sequenza.

Se la realizzazione dell'elenco genera un'eccezione, non potrai mai agire su un singolo elemento.

Se l'enumerazione è infinita (come i numeri naturali), sei sfortunato.

Me lo sono sempre chiesto anch'io, ecco perché lo porto sempre con me:

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    foreach (var item in col)
    {
        action(item);
    }
}

Bel metodo di estensione.

Pertanto ci sono stati molti commenti sul fatto che un metodo di estensione ForEach non è appropriato perché non restituisce un valore come i metodi di estensione LINQ.Sebbene questa sia un'affermazione fattuale, non è del tutto vera.

I metodi di estensione LINQ restituiscono tutti un valore in modo che possano essere concatenati insieme:

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

Tuttavia, solo perché LINQ è implementato utilizzando metodi di estensione non significa che i metodi di estensione dovere essere utilizzato allo stesso modo e restituire un valore.Scrivere un metodo di estensione per esporre funzionalità comuni che non restituiscono un valore è un utilizzo perfettamente valido.

L'argomentazione specifica su ForEach è che, in base ai vincoli sui metodi di estensione (vale a dire che un metodo di estensione sarà Mai sovrascrivere un metodo ereditato con il metodo stessa firma), potrebbe verificarsi una situazione in cui il metodo di estensione personalizzato è disponibile su tutte le classi che implementano IEnumerable<T>tranne Elenco<T>.Ciò può causare confusione quando i metodi iniziano a comportarsi in modo diverso a seconda che venga chiamato o meno il metodo di estensione o il metodo di eredità.

Potresti usare (concatenabile, ma valutato pigramente) Select, prima eseguendo l'operazione e poi restituendo l'identità (o qualcos'altro se preferisci)

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

Dovrai assicurarti che sia ancora valutato, sia con Count() (l'operazione più economica da elencare afaik) o un'altra operazione di cui avevi comunque bisogno.

Mi piacerebbe vederlo portato nella libreria standard però:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

Il codice sopra diventa quindi people.WithLazySideEffect(p => Console.WriteLine(p)) che è effettivamente equivalente a foreach, ma pigro e concatenabile.

@Coincoin

Il vero potere del metodo di estensione foreach riguarda la riutilizzabilità del file Action<> senza aggiungere metodi non necessari al codice.Supponiamo che tu abbia 10 elenchi e desideri eseguire la stessa logica su di essi e che una funzione corrispondente non si adatti alla tua classe e non venga riutilizzata.Invece di avere dieci cicli for, o una funzione generica che è ovviamente un helper che non appartiene, puoi mantenere tutta la tua logica in un unico posto (il Action<>.Quindi, dozzine di righe vengono sostituite con

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

eccetera...

La logica è in un unico posto e non hai inquinato la tua classe.

Si noti che il AltroLINQ NuGet fornisce il file ForEach metodo di estensione che stai cercando (oltre a a Pipe metodo che esegue il delegato e restituisce il risultato).Vedere:

La maggior parte dei metodi di estensione LINQ restituiscono risultati.ForEach non rientra in questo schema poiché non restituisce nulla.

Puoi usare select quando vuoi restituire qualcosa.In caso contrario, puoi prima utilizzare ToList, perché probabilmente non vorrai modificare nulla nella raccolta.

Ho scritto un post sul blog a riguardo:http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

Puoi votare qui se desideri vedere questo metodo in .NET 4.0:http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093

Nella versione 3.5, tutti i metodi di estensione aggiunti a IEnumerable sono disponibili per il supporto LINQ (da notare che sono definiti nella classe System.Linq.Enumerable).In questo post spiego perché foreach non appartiene a LINQ:Metodo di estensione LINQ esistente simile a Parallel.For?

Sono io o List<T>.Foreach è stato praticamente reso obsoleto da Linq.Originariamente c'era

foreach(X x in Y) 

dove Y doveva semplicemente essere IEnumerable (pre 2.0) e implementare un GetEnumerator().Se guardi l'MSIL generato puoi vedere che è esattamente lo stesso di

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

(Vedere http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx per la MSIL)

Poi in DotNet2.0 sono arrivati ​​i Generics e List.Foreach mi è sempre sembrato un'implementazione del modello Vistor (vedi Design Patterns di Gamma, Helm, Johnson, Vlissides).

Ora ovviamente nella versione 3.5 possiamo invece utilizzare un Lambda con lo stesso effetto, provatelo ad esempiohttp://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-my/

Vorrei approfondire La risposta di Aku.

Se vuoi chiamare un metodo al solo scopo del suo effetto collaterale senza prima iterare l'intero enumerabile, puoi usare questo:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}

Se disponi di F# (che sarà disponibile nella prossima versione di .NET), puoi utilizzare

Seq.iter doSomething myIEnumerable

In parte è perché i progettisti del linguaggio non sono d'accordo con esso da un punto di vista filosofico.

  • Non avere (e testare...) una funzionalità richiede meno lavoro che averla.
  • Non è realmente più breve (ci sono alcuni casi di funzioni di passaggio in cui lo è, ma non sarebbe l'uso principale).
  • Il suo scopo è avere effetti collaterali, che non è ciò di cui parla linq.
  • Perché avere un altro modo per fare la stessa cosa rispetto a una funzionalità che abbiamo già?(per ogni parola chiave)

https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

Per utilizzare Foreach, l'elenco deve essere caricato nella memoria primaria.Ma a causa della natura del caricamento lento di IEnumerable, non forniscono ForEach in IEnumerable.

Tuttavia, caricando rapidamente (usando ToList()), puoi caricare la tua lista in memoria e sfruttare ForEach.

Nessuno ha ancora sottolineato che ForEach<T> comporta un controllo del tipo in fase di compilazione in cui la parola chiave foreach viene controllata in fase di esecuzione.

Avendo eseguito un po' di refactoring in cui entrambi i metodi sono stati utilizzati nel codice, preferisco .ForEach, poiché ho dovuto cercare errori di test/errori di runtime per trovare i problemi foreach.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top