C # foreach miglioramenti?


9

Mi imbatto spesso in questo durante la programmazione in cui voglio avere un indice di conteggio dei cicli all'interno di un foreach e devo creare un numero intero, usarlo, incrementare, ecc. Non sarebbe una buona idea se ci fosse una parola chiave introdotta che era il conteggio dei loop all'interno di una foreach? Potrebbe essere utilizzato anche in altri loop.

Questo va contro l'uso delle parole chiave visto che il compilatore non permetterebbe che questa parola chiave venga usata da nessuna parte ma in un costrutto a ciclo?


7
Puoi farlo da solo con un metodo di estensione, vedi la risposta di Dan Finch: stackoverflow.com/a/521894/866172 (non la risposta più votata, ma penso che sia quella "più pulita")
Jalayn,

Mi vengono in mente Perl e cose come $ _. Odio quella roba.
Yam Marcovic,

2
Da quello che so di C #, ha molte parole chiave basate sul contesto, quindi questo non "andrebbe contro l'uso delle parole chiave". Come indicato in una risposta di seguito, tuttavia, probabilmente si desidera solo utilizzare un for () ciclo.
Craige,

@Jalayn Per favore, inseriscilo come risposta. È uno dei migliori approcci.
Apoorv Khurasia,

@MonsterTruck: non penso che dovrebbe. La vera soluzione sarebbe migrare questa domanda su SO e chiuderla come duplicato della domanda a cui Jalayn si collega.
Brian,

Risposte:


54

Se hai bisogno di un conteggio dei loop all'interno di un foreachloop, perché non usi semplicemente un forloop normale . Il foreachloop aveva lo scopo di semplificare gli usi specifici dei forloop. Sembra che tu abbia una situazione in cui la semplicità del foreachnon è più vantaggiosa.


1
+1 - buon punto. È abbastanza facile usare IEnumerable.Count o object.length con un ciclo for regolare per evitare problemi nel capire il numero di elementi nell'oggetto.

11
@ GlenH7: a seconda dell'implementazione, IEnumerable.Countpotrebbe essere costoso (o impossibile, se si tratta di una sola volta enumerazione). Devi sapere qual è la fonte sottostante prima di poterlo fare in sicurezza.
Binary Worrier,

2
-1: Bill Wagner nel più efficace C # descrive il motivo per cui si dovrebbe sempre attenersi a foreachsopra for (int i = 0…). Ha scritto un paio di pagine ma posso riassumere in due parole: ottimizzazione del compilatore su iterazione. Ok, non erano due parole.
Apoorv Khurasia,

13
@MonsterTruck: qualunque cosa abbia scritto Bill Wagner, ho visto abbastanza situazioni come quella descritta dall'OP, in cui un forciclo fornisce un codice leggibile meglio (e l'ottimizzazione teorica del compilatore è spesso ottimizzazione prematura).
Doc Brown,

1
@DocBrown Non è così semplice come l'ottimizzazione prematura. Mi sono identificato colli di bottiglia in passato e li ho risolti usando questo approccio (ma ammetterò che avevo a che fare con migliaia di oggetti nella collezione). Spero di pubblicare presto un esempio funzionante. Nel frattempo, ecco Jon Skeet . [Dice che il miglioramento delle prestazioni non sarà significativo, ma dipende molto dalla raccolta e dal numero di oggetti].
Apoorv Khurasia,

37

Puoi usare tipi anonimi come questo

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

Questo è simile alla enumeratefunzione di Python

for i, v in enumerate(items):
    print v, i

+1 Oh ... lampadina. Mi piace questo approccio. Perché non ci avevo mai pensato prima?
Phil

17
Chiunque preferisca questo in C # piuttosto che un classico per loop ha chiaramente una strana opinione sulla leggibilità. Questo potrebbe essere un trucco accurato ma non permetterei qualcosa del genere nel codice di qualità della produzione. È molto più rumoroso di un classico per loop e non può essere compreso come veloce.
Falcon,

4
@Falcon, questa è una valida alternativa SE NON PUOI UTILIZZARE una vecchia moda per loop (che ha bisogno di un conteggio). Sono alcune raccolte di oggetti su cui ho dovuto lavorare ...
Fabricio Araujo,

8
@FabricioAraujo: Sicuramente C # per i loop non richiede un conteggio. Per quanto ne so sono uguali a quelli di C per i loop, il che significa che è possibile utilizzare qualsiasi valore booleano per terminare il loop. Ad esempio un controllo per la fine dell'enumerazione quando MoveNext restituisce false.
Zan Lynx,

1
Questo ha l'ulteriore vantaggio di avere tutto il codice per gestire l'incremento all'inizio del blocco foreach invece di doverlo trovare.
JeffO

13

Sto solo aggiungendo la risposta di Dan Finch qui, come richiesto.

Non darmi punti, dai punti a Dan Finch :-)

La soluzione è scrivere il seguente metodo di estensione generico:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Che viene utilizzato in questo modo:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );

2
È abbastanza intelligente, ma penso che usare un normale per sia più "leggibile", specialmente se lungo la strada qualcuno che finisce per mantenere il codice potrebbe non essere un asso in .NET e non avere familiarità con il concetto di metodi di estensione . Sto ancora afferrando questo pezzetto di codice però. :)
MetalMikester

1
Potrei discutere di entrambi i lati di questo, e della domanda originale: sono d'accordo con l'intento dei poster, ci sono casi in cui foreach legge meglio ma è necessario un indice, ma sono sempre contrario all'aggiunta di più zucchero sintattico in una lingua se non lo è gravemente necessario - Ho esattamente la stessa soluzione nel mio codice con un nome diverso - stringhe.ApplyIndexed <T> (......)
Mark Mullin

Sono d'accordo con Mark Mullin, puoi discutere di entrambe le parti. Ho pensato che fosse un uso abbastanza intelligente dei metodi di estensione e rispondeva alla domanda, quindi l'ho condivisa.
Jalayn,

5

Potrei sbagliarmi su questo, ma ho sempre pensato che il punto principale del ciclo foreach era semplificare l'iterazione dai giorni STL del C ++ cioè

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

confrontare questo con l'uso di .NET di IEnumerable nel foreach

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

quest'ultimo è molto più semplice ed evita molte delle vecchie insidie. Inoltre, sembrano seguire regole quasi identiche. Ad esempio, non è possibile modificare il contenuto di IEnumerable mentre si è all'interno del ciclo (il che ha molto più senso se lo si pensa in modo STL). Tuttavia, il ciclo for ha spesso uno scopo logico diverso dall'iterazione.


4
+1: poche persone ricordano che la foreach è fondamentalmente zucchero sintattico sul modello iteratore.
Steven Evers,

1
Bene ci sono alcune differenze; manipolazione del puntatore dove presente in .NET è nascosto abbastanza profondamente dal programmatore, ma potresti scrivere un ciclo for che assomiglia molto all'esempio C ++:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS

@KeithS, non se ti rendi conto che tutto ciò che tocchi in .NET che non è un tipo strutturato È UN PUNTATORE, solo con una sintassi diversa (. Anziché ->). In effetti, passare un tipo di riferimento a un metodo equivale a passarlo come puntatore e passarlo per riferimento significa passarlo come doppio puntatore. Stessa cosa. Almeno, questo è il modo in cui una persona che è la lingua madre C ++ ci pensa. In un certo senso, impari lo spagnolo a scuola pensando a come la lingua è sintatticamente correlata alla tua lingua madre.
Jonathan Henson,

* tipo di valore non strutturato. scusa.
Jonathan Henson,

2

Se la raccolta in questione è una IList<T>, puoi semplicemente utilizzare forcon l'indicizzazione:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

In caso contrario, è possibile utilizzare Select(), ha un sovraccarico che ti dà l'indice.

Se anche questo non è adatto, penso che mantenere quell'indice manualmente sia abbastanza semplice, sono solo due righe di codice molto brevi. La creazione di una parola chiave specifica per questo sarebbe eccessiva.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}

+1, inoltre, se si utilizza un indice esattamente una volta all'interno del ciclo, è possibile fare qualcosa di simile foo(++i)o in foo(i++)base all'indice necessario.
Giobbe

2

Se tutto ciò che sai è che la raccolta è un IEnumerable, ma devi tenere traccia di quanti elementi hai elaborato finora (e quindi il totale quando hai finito), puoi aggiungere un paio di righe a un ciclo for base:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

Puoi anche aggiungere una variabile indice incrementata a un foreach:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

Se vuoi rendere questo aspetto più elegante, puoi definire un metodo di estensione o due per "nascondere" questi dettagli:

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

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));

1

Lo scoping funziona anche se vuoi mantenere il blocco pulito dalle collisioni con altri nomi ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

Per questo uso un whileloop. Anche i loop forfunzionano e molte persone sembrano preferirli, ma mi piace solo usare forqualcosa che avrà un numero fisso di iterazioni. IEnumerables può essere generato in fase di esecuzione, quindi, a mio avviso, whileha più senso. Chiamalo una linea guida di codifica informale, se vuoi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.