Come si ottiene l'indice dell'iterazione corrente di un ciclo foreach?


939

Esiste un costrutto di linguaggio raro che non ho riscontrato (come i pochi che ho imparato di recente, alcuni su Stack Overflow) in C # per ottenere un valore che rappresenta l'attuale iterazione di un ciclo foreach?

Ad esempio, attualmente faccio qualcosa del genere a seconda delle circostanze:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

1
in genere il recupero del casting foreach non mi consentirà di essere più ottimizzato del semplice utilizzo dell'accesso basato sull'indice in una raccolta, sebbene in molti casi sarà uguale. Lo scopo di foreach è quello di rendere leggibile il tuo codice, ma (di solito) aggiunge un livello di riferimento indiretto, che non è gratuito.
Brian,

8
Direi che lo scopo principale di foreachè fornire un meccanismo di iterazione comune per tutte le raccolte indipendentemente dal fatto che siano indicizzabili ( List) o meno ( Dictionary).
Brian Gideon,

2
Ciao Brian Gideon - sono assolutamente d'accordo (questo era qualche anno fa e all'epoca ero molto meno esperto). Tuttavia, sebbene Dictionarynon sia indicizzabile, un'iterazione Dictionarylo attraversa in un ordine particolare (ovvero un Enumeratore è indicizzabile dal fatto che produce elementi in sequenza). In questo senso, potremmo dire che non stiamo cercando l'indice all'interno della raccolta, ma piuttosto l'indice dell'elemento elencato corrente all'interno dell'enumerazione (ovvero se siamo al primo, quinto o ultimo elemento elencato).
Matt Mitchell,

4
foreach consente inoltre al compilatore di saltare i limiti controllando l'accesso a ciascun array nel codice compilato. L'uso di con un indice farà verificare al runtime se l'accesso all'indice è sicuro.
IvoTops,

1
Ma è falso. Se non modifichi la variabile di iterazione di un ciclo for all'interno del ciclo, il compilatore sa quali sono i suoi limiti e non ha bisogno di controllarli di nuovo. Questo è un caso così comune che qualsiasi compilatore decente lo implementerà.
Jim Balter,

Risposte:


552

Il foreachè per iterare su collezioni che implementano IEnumerable. Lo fa chiamando GetEnumeratorla raccolta, che restituirà un Enumerator.

Questo enumeratore ha un metodo e una proprietà:

  • MoveNext ()
  • attuale

Currentrestituisce l'oggetto su cui si trova Enumerator, si MoveNextaggiorna Currentall'oggetto successivo.

Il concetto di un indice è estraneo al concetto di enumerazione e non può essere fatto.

Per questo motivo, è possibile attraversare la maggior parte delle raccolte utilizzando un indicizzatore e il costrutto loop for.

Preferisco di gran lunga usare un ciclo for in questa situazione rispetto al tracciamento dell'indice con una variabile locale.


165
"Ovviamente, il concetto di un indice è estraneo al concetto di enumerazione e non può essere fatto." - Questa è una sciocchezza, come chiariscono le risposte di David B e Bcahill. Un indice è un'enumerazione su un intervallo e non c'è motivo per cui non si possano enumerare due cose in parallelo ... è esattamente ciò che fa la forma di indicizzazione di Enumerable.Select.
Jim Balter,

11
Esempio di codice di base:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock,

22
@JimBalter: questo è il primo link che ho letto. Non vedo come supporti la tua posizione. Inoltre, non rispondo ad ad-homs e parole di divisione alle persone quando rispondo. Cito ad esempio il tuo uso di "sciocchezze", "straordinaria confusione", "completamente sbagliato e confuso", "Ho 37 voti positivi", ecc. (Penso che il tuo ego sia un po 'in linea qui). Invece, io Vorrei chiederti cortesemente di non battere gli altri con il tuo 'topicum ad verecundiam' e vorrei invece incoraggiarti a pensare a modi per supportare le persone qui su StackOverflow. Direi che Jon Skeet è un cittadino modello in questo senso.
Pretzel,

11
Pretzel: il tuo uso di Jon Skeet come cittadino modello è errato, in quanto batterà (e ridimensionerà) qualcuno che non è d'accordo con lui, come ho sperimentato. Jim Balter: Alcuni dei tuoi commenti sono stati eccessivamente aggressivi e avresti potuto presentare i tuoi punti con meno rabbia e più istruzione.
Suncat2000,

7
@Pretzel Il punto di Jim è che finché puoi mappare gli elementi su una sequenza di numeri interi, puoi indicizzarli. Che la classe stessa non memorizzi un indice è accanto al punto. Inoltre, che la lista collegata ha avere un ordine che rafforzare la posizione di Jim. Tutto quello che devi fare è numerare ogni elemento in ordine. In particolare, è possibile ottenere questo risultato incrementando un conteggio durante l'iterazione oppure è possibile generare un elenco di numeri interi con la stessa lunghezza e quindi comprimerli (come nella zipfunzione di Python ).
jpmc26,

666

Ian Mercer ha pubblicato una soluzione simile a questa sul blog di Phil Haack :

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

Questo ti dà l'oggetto ( item.value) e il suo indice ( item.i) usando questo sovraccarico di LINQSelect :

il secondo parametro della funzione [all'interno di Select] rappresenta l'indice dell'elemento source.

La new { i, value }sta creando un nuovo oggetto anonimo .

Le allocazioni di heap possono essere evitate utilizzando ValueTuplese si utilizza C # 7.0 o versioni successive:

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

Puoi anche eliminare item.utilizzando la destrutturazione automatica:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

9
Questa soluzione è utile per il caso di un modello Razor in cui la pulizia del modello è un problema di progettazione non banale e si desidera utilizzare anche l'indice di ogni elemento elencato. Tuttavia, tieni presente che l'allocazione degli oggetti da 'wrapping' aggiunge spese (nello spazio e nel tempo) oltre all'incremento (inevitabile) di un numero intero.
David Bullock,

6
@mjsr La documentazione è qui .
Thorkil Holm-Jacobsen,

19
Scusate, è intelligente, ma è davvero più leggibile che creare un indice al di fuori della foreach e incrementarlo ad ogni ciclo?
jbyrd,

13
Con le versioni successive di C # in modo da utilizzare anche le tuple, quindi avrai qualcosa del genere: foreach (var (item, i) in Model.Select ((v, i) => (v, i))) Ti permette di accedere all'elemento e all'indice (i) direttamente all'interno del for-loop con decostruzione della tupla.
Haukman,

5
Qualcuno può spiegarmi perché questa è una buona risposta (oltre 450 voti al momento della stesura)? Per quanto posso vedere è più difficile da capire che semplicemente incrementare un contatore, è quindi meno gestibile, utilizza più memoria ed è probabilmente più lento. Mi sto perdendo qualcosa?
Rich N

182

Infine C # 7 ha una sintassi decente per ottenere un indice all'interno di un foreachciclo (cioè tuple):

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

Sarebbe necessario un piccolo metodo di estensione:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 

8
Questa risposta è sottovalutata, avendo le tuple molto più pulite
Todd

7
Modificato per gestire raccolte null:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Ricarica il

Ben fatto. Questa soluzione mi piace molto di più.
FranzHuber23,

Questa è la risposta migliore
w0ns88,

2
Potrebbe essere utile chiamare il metodo Enumeratedper essere più riconoscibile per le persone abituate ad altre lingue (e forse scambiare anche l'ordine dei parametri della tupla). Non WithIndexè ovvio comunque.
FernAndr

115

Potrebbe fare qualcosa del genere:

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}

12
Ciò non "realmente" risolve il problema. L'idea è buona ma non evita la variabile di conteggio aggiuntiva
Atmocreations

Questo non funziona se abbiamo una dichiarazione di ritorno all'interno del nostro ciclo for, se cambi "ForEachWithIndex" per questo, allora non è generico, meglio scrivere un normale ciclo
Shankar Raju

La tua chiamata ForEachWithIndex è equivalente a questa usando Linq Select che accetta una stringa e un indice:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861

95

Non sono d'accordo con i commenti sul fatto che un forloop sia una scelta migliore nella maggior parte dei casi.

foreachè un costrutto utile e non sostituibile da un forciclo in tutte le circostanze.

Ad esempio, se si dispone di un DataReader e si esegue il ciclo di tutti i record utilizzando un metodo, foreachquesto chiama automaticamente il metodo Dispose e chiude il lettore (che può quindi chiudere automaticamente la connessione). Questo è quindi più sicuro in quanto impedisce perdite di connessione anche se si dimentica di chiudere il lettore.

(Sicuramente è buona pratica chiudere sempre i lettori ma il compilatore non lo catturerà se non lo fai - non puoi garantire di aver chiuso tutti i lettori ma puoi rendere più probabile che non perderai connessioni ottenendo con l'abitudine di usare foreach.)

Potrebbero esserci altri esempi di utile richiamo implicito del Disposemetodo.


2
Grazie per averlo segnalato. Piuttosto sottile. È possibile ottenere ulteriori informazioni a pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator e msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx .
Mark Meuer,

+1. Stavo scrivendo più in dettaglio su come foreachè diverso da for(e più vicino a while) su Programmers.SE .
Arseni Mourzenko,

64

Risposta letterale - avviso, le prestazioni potrebbero non essere buone come il semplice utilizzo di un intper tenere traccia dell'indice. Almeno è meglio dell'uso IndexOf.

Devi solo utilizzare il sovraccarico di indicizzazione di Seleziona per avvolgere ogni elemento della raccolta con un oggetto anonimo che conosce l'indice. Questo può essere fatto contro tutto ciò che implementa IEnumerable.

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}

3
L'unico motivo per usare OfType <T> () invece di Cast <T> () è se alcuni elementi dell'enumerazione potrebbero fallire un cast esplicito. Per oggetto, questo non sarà mai il caso.
Dahlbyk,

13
Certo, tranne per l'altro motivo per usare OfType invece di Cast - che è che non uso mai Cast.
Amy B,

36

Usando LINQ, C # 7 e il System.ValueTuplepacchetto NuGet, puoi fare questo:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

È possibile utilizzare il foreachcostrutto regolare ed essere in grado di accedere direttamente al valore e all'indice, non come membro di un oggetto, e mantenere entrambi i campi solo nell'ambito del ciclo. Per questi motivi, credo che questa sia la soluzione migliore se si è in grado di utilizzare C # 7 e System.ValueTuple.


In che cosa differisce dalla risposta dell'utente1414213562 ?
Edward Brey,

@EdwardBrey Suppongo che non lo sia. Questa domanda ha molte risposte, probabilmente mi è sfuggita o non ho notato cosa stesse facendo perché ha separato parte della logica in un metodo di estensione.
Pavel

1
Questo è diverso perché .Select è integrato da LINQ. Non devi scrivere la tua funzione? Tuttavia, dovrai installare VS "System.ValueTuple".
Anton,

33

Usando la risposta di @ FlySwat, ho trovato questa soluzione:

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

Ottieni l'enumeratore utilizzando GetEnumeratore quindi esegui il ciclo utilizzando un forciclo. Tuttavia, il trucco è rendere le condizioni del loop listEnumerator.MoveNext() == true.

Poiché il MoveNextmetodo di un enumeratore restituisce true se esiste un elemento successivo e vi si può accedere, facendo in modo che la condizione del ciclo interrompa il ciclo quando si esauriscono gli elementi per iterare.


10
Non è necessario confrontare listEnumerator.MoveNext () == true. È come chiedere al computer se true == true? :) Dì solo se listEnumerator.MoveNext () {}
Zesty

9
@Zesty, hai assolutamente ragione. Ho sentito che è più leggibile aggiungerlo in questo caso, specialmente per le persone che non sono abituate a inserire qualcosa di diverso da me come condizione.
Gezim,

@Gezim Non mi dispiace predicati qui, ma capisco che questo non sembra un predicato.
John Dvorak,

1
È necessario disporre l'enumeratore.
Antonín Lejsek,

@ AntonínLejsek L'enumeratore non implementa IDisposable.
Edward Brey,

27

Non c'è niente di sbagliato nell'usare una variabile contatore. In effetti, sia che tu usi for, foreach whileo do, una variabile contatore deve essere dichiarata e incrementata da qualche parte.

Quindi usa questo linguaggio se non sei sicuro di avere una collezione opportunamente indicizzata:

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

Altrimenti usa questo se sai che la tua raccolta indicizzabile è O (1) per l'accesso all'indice (che sarà per Arraye probabilmente per List<T>(la documentazione non dice), ma non necessariamente per altri tipi (come LinkedList)):

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

Non dovrebbe mai essere necessario operare "manualmente" IEnumeratorinvocando MoveNext()e interrogando Current- ti foreachsta risparmiando quel fastidio particolare ... se hai bisogno di saltare gli oggetti, usa semplicemente un continuenel corpo del loop.

E solo per completezza, a seconda di ciò che stavi facendo con il tuo indice (i costrutti di cui sopra offrono molta flessibilità), potresti usare Parallel LINQ:

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

Usiamo AsParallel()sopra, perché è già il 2014, e vogliamo fare buon uso di quei core multipli per accelerare le cose. Inoltre, per LINQ 'sequenziale', ottieni solo un ForEach()metodo di estensione List<T>eArray ... e non è chiaro che usarlo sia meglio di fare un semplice foreach, poiché stai ancora eseguendo un thread singolo per una sintassi più brutta.


26

È possibile racchiudere l'enumeratore originale con un altro che contenga le informazioni sull'indice.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

Ecco il codice per la ForEachHelperclasse.

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}

Questo non restituirà effettivamente l'indice dell'articolo. Restituirà invece l'indice all'interno dell'elenco elencato, che può essere solo un elenco secondario dell'elenco, fornendo in tal modo dati accurati solo quando l'elenco secondario e l'elenco hanno le stesse dimensioni. Fondamentalmente, ogni volta che la raccolta contiene oggetti non nel tipo richiesto, l'indice sarà errato.
Lucas B,

7
@Lucas: No, ma restituirà l'indice dell'attuale iterazione foreach. Questa era la domanda.
Brian Gideon,

20

Ecco una soluzione che ho appena escogitato per questo problema

Codice originale:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

Codice aggiornato

enumerable.ForEach((item, index) => blah(item, index));

Metodo di estensione:

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }

16

Aggiungi il tuo indice. Mantienilo semplice.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

15

Funzionerà solo per un elenco e non per IEnumerable, ma in LINQ c'è questo:

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@Jonathan Non ho detto che è stata un'ottima risposta, ho solo detto che stava solo mostrando che era possibile fare quello che aveva chiesto :)

@Graphain Non mi aspetto che sia veloce - non sono del tutto sicuro di come funzioni, potrebbe reiterare l'intero elenco ogni volta per trovare un oggetto corrispondente, che sarebbe un inferno di paragoni.

Detto questo, List potrebbe mantenere un indice di ciascun oggetto insieme al conteggio.

Jonathan sembra avere un'idea migliore, se elaborasse?

Sarebbe meglio tenere conto di dove ti trovi nella foreach, più semplice e più adattabile.


5
Non sono sicuro del pesante downvoting. Certamente le prestazioni lo rendono proibitivo, ma hai risposto alla domanda!
Matt Mitchell,

4
Un altro problema è che funziona solo se gli elementi nell'elenco sono unici.
CodesInChaos,

14

C # 7 ci offre finalmente un modo elegante per farlo:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

Sì, e MS dovrebbe estendere CLR / BCL per rendere nativo questo tipo di cose.
Todd,

14

Perché foreach ?!

Il modo più semplice è utilizzare per anziché foreach se si utilizza Elenco :

for (int i = 0 ; i < myList.Count ; i++)
{
    // Do something...
}

O se vuoi usare foreach:

foreach (string m in myList)
{
     // Do something...
}

Puoi usarlo per conoscere l'indice di ogni ciclo:

myList.indexOf(m)

8
La soluzione indexOf non è valida per l'elenco con duplicati ed è anche molto lenta.
Tymtam,

2
Il problema da evitare è dove attraversi IEnumerable più volte, ad esempio per ottenere il conteggio degli articoli e quindi ogni articolo. Ciò ha implicazioni quando, ad esempio, IEnumerable è il risultato di una query del database.
David Clarke,

2
myList.IndexOf () è O (n), quindi il tuo ciclo sarà O (n ^ 2).
Patrick Beard,

9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Ciò funzionerebbe per il supporto delle raccolte IList.


68
Due problemi: 1) Questo O(n^2)perché nella maggior parte delle implementazioni lo IndexOfè O(n). 2) Ciò non riesce se ci sono elementi duplicati nell'elenco.
CodesInChaos,

15
Nota: O (n ^ 2) significa che potrebbe essere disastrosamente lento per una grande raccolta.
O'Rooney,

Grande invenzione per usare il metodo IndexOf! Questo è quello che stavo cercando per ottenere l'indice (numero) nel ciclo foreach! Big Thx
Mitja Bonca,

19
Dio, spero che tu non l'abbia usato! :( UTILIZZA quella variabile che non volevi creare - in effetti, creerà n + 1 ints perché quella funzione deve crearne una anche per restituirla - e quell'indice di ricerca è molto, molto più lento di un'operazione di incremento di un numero intero in ogni passaggio. Perché le persone non voteranno questa risposta?
Canahari,

13
Non usare questa risposta, ho trovato la dura verità menzionata in uno dei commenti. "Questo non riesce se ci sono elementi duplicati nell'elenco." !!!
Bruce,

9

È così che lo faccio, il che è bello per la sua semplicità / brevità, ma se stai facendo molto nel corpo del loop obj.Value, invecchierà abbastanza velocemente.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}

8

Questa risposta: fai pressione sul team linguistico C # per il supporto diretto del linguaggio.

La risposta principale afferma:

Ovviamente, il concetto di un indice è estraneo al concetto di enumerazione e non può essere fatto.

Sebbene ciò sia vero per l'attuale versione del linguaggio C # (2020), questo non è un limite CLR / linguaggio concettuale, ma può essere fatto.

Il team di sviluppo del linguaggio C # di Microsoft potrebbe creare una nuova funzionalità del linguaggio C #, aggiungendo il supporto per una nuova interfaccia IIndexedEnumerable

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

Se foreach ()viene utilizzato ed with var indexè presente, il compilatore si aspetta che la raccolta articoli dichiari l' IIndexedEnumerableinterfaccia. Se l'interfaccia è assente, il compilatore può eseguire il polyfill del wrapping dell'origine con un oggetto IndexedEnumerable, che aggiunge il codice per tenere traccia dell'indice.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

Successivamente, il CLR può essere aggiornato per avere il tracciamento dell'indice interno, che viene utilizzato solo se withviene specificata la parola chiave e l'origine non viene implementata direttamenteIIndexedEnumerable

Perché:

  • Foreach sembra più bello e, nelle applicazioni aziendali, i loop foreach raramente rappresentano un collo di bottiglia nelle prestazioni
  • Foreach può essere più efficiente in memoria. Avere una pipeline di funzioni invece di convertirle in nuove raccolte ad ogni passaggio. A chi importa se utilizza alcuni cicli di CPU in più quando ci sono meno errori della cache della CPU e meno garbage collection?
  • Richiedere al programmatore di aggiungere il codice di monitoraggio dell'indice, rovina la bellezza
  • È abbastanza facile da implementare (per favore Microsoft) ed è retrocompatibile

Mentre la maggior parte delle persone qui non sono dipendenti Microsoft, questa è una risposta corretta, è possibile fare pressione su Microsoft per aggiungere una tale funzionalità. Potresti già creare il tuo iteratore con una funzione di estensione e utilizzare le tuple , ma Microsoft potrebbe cospargere lo zucchero sintattico per evitare la funzione di estensione


Aspetta, quindi questa funzionalità linguistica esiste già o viene proposta per il futuro?
Pavel,

1
@Pavel Ho aggiornato la risposta per essere chiara. Questa risposta è stata fornita per contrastare la risposta principale che afferma "Ovviamente, il concetto di un indice è estraneo al concetto di enumerazione e non può essere fatto".
Todd,

6

Se la raccolta è un elenco, è possibile utilizzare List.IndexOf, come in:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

13
E ora l'algoritmo è O (n ^ 2) (se non peggio). Vorrei riflettere molto attentamente prima di utilizzare questo. È anche un duplicato della risposta di
@crucible

1
@BradleyDotNET ha ragione, non usare questa versione.
Oskar,

1
Stai attento con questo! Se hai un oggetto duplicato nella tua lista, otterrà la posizione del primo!
Sonhja,

5

Meglio usare continueuna costruzione sicura delle parole chiave come questa

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

5

Puoi scrivere il tuo ciclo in questo modo:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

Dopo aver aggiunto il seguente metodo struct ed extension.

Il metodo struct ed extension incapsula la funzionalità Enumerable.Select.

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}

3

La mia soluzione a questo problema è un metodo di estensione WithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

Usalo come

var list = new List<int> { 1, 2, 3, 4, 5, 6 };    

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

Userei a structper la coppia (indice, oggetto).
CodesInChaos,

3

Per interesse, Phil Haack ha appena scritto un esempio di questo nel contesto di un delegato templato Razor ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

In effetti scrive un metodo di estensione che avvolge l'iterazione in una classe "IteratedItem" (vedi sotto) consentendo l'accesso all'indice e all'elemento durante l'iterazione.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

Tuttavia, mentre ciò andrebbe bene in un ambiente non Razor se si sta eseguendo una singola operazione (ovvero una che potrebbe essere fornita come lambda) non sarà una solida sostituzione della sintassi for / foreach in contesti non Razor .


3

Non penso che questo dovrebbe essere abbastanza efficiente, ma funziona:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

3

Ho creato questo in LINQPad :

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

Puoi anche semplicemente usare string.join:

var joinResult = string.Join(",", listOfNames);

Puoi anche usare string.join in c # Ad esempio: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance,

1
Qualcuno può spiegarmi cos'è questa cosa O (n * n)?
Axel,

@Axel significa sostanzialmente che le operazioni necessarie per calcolare il risultato aumentano in modo quadratico, cioè se ci sono nelementi allora le operazioni sono n * n, o n-quadrate. en.wikipedia.org/wiki/…
Richard Hansell,

2

Non credo che ci sia un modo per ottenere il valore dell'iterazione corrente di un ciclo foreach. Contare te stesso, sembra essere il modo migliore.

Posso chiederti, perché vorresti saperlo?

Sembra che molto probabilmente faresti una delle tre cose:

1) Ottenere l'oggetto dalla raccolta, ma in questo caso lo hai già.

2) Conteggio degli oggetti per successive elaborazioni successive ... le raccolte hanno una proprietà Count che puoi usare.

3) Impostare una proprietà sull'oggetto in base al suo ordine nel ciclo ... anche se potresti facilmente impostarlo quando hai aggiunto l'oggetto alla raccolta.


4) Il caso che ho toccato più volte è qualcosa di diverso che deve essere fatto al primo o all'ultimo passaggio: dì un elenco di oggetti che stai per stampare e hai bisogno di virgole tra gli oggetti ma non dopo l'ultimo.
Loren Pechtel,

2

A meno che la tua raccolta non possa restituire l'indice dell'oggetto tramite un metodo, l'unico modo è usare un contatore come nel tuo esempio.

Tuttavia, quando si lavora con gli indici, l'unica risposta ragionevole al problema è utilizzare un ciclo for. Qualsiasi altra cosa introduce la complessità del codice, per non parlare della complessità del tempo e dello spazio.


2

Che ne dici di questo? Nota che myDelimitedString può essere nullo se myEnumerable è vuoto.

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}

Problemi multipli qui. A) tutore extra. B) string concat + = iterazione per loop.
enorl76,

2

Ho appena avuto questo problema, ma pensare al problema nel mio caso ha dato la soluzione migliore, non correlata alla soluzione prevista.

Potrebbe essere un caso abbastanza comune, in sostanza, sto leggendo da un elenco di sorgenti e creando oggetti basati su di essi in un elenco di destinazioni, tuttavia, devo verificare se gli elementi di origine sono validi per primi e voglio restituire la riga di qualsiasi errore. A prima vista, voglio ottenere l'indice nell'enumeratore dell'oggetto nella proprietà Current, tuttavia, mentre sto copiando questi elementi, conosco implicitamente l'indice corrente comunque dalla destinazione corrente. Ovviamente dipende dall'oggetto di destinazione, ma per me era un elenco e molto probabilmente implementerà ICollection.

vale a dire

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

Non sempre applicabile, ma spesso abbastanza per essere degno di nota, penso.

Comunque, il punto è che a volte c'è già una soluzione non ovvia nella logica che hai ...


2

Non ero sicuro di cosa stavi cercando di fare con le informazioni sull'indice in base alla domanda. Tuttavia, in C #, di solito è possibile adattare il metodo IEnumerable.Select per estrarre l'indice da ciò che si desidera. Ad esempio, potrei usare qualcosa del genere per stabilire se un valore è pari o dispari.

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Questo ti darebbe un dizionario per nome se l'elemento era dispari (1) o pari (0) nell'elenco.

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.