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

C’è qualche raro costrutto linguistico che non ho incontrato (come i pochi che ho imparato di recente, alcuni su Stack Overflow) in C # per ottenere un valore che rappresenta l’iterazione corrente di un ciclo foreach?

Ad esempio, attualmente faccio qualcosa di simile a seconda delle circostanze:

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

Il foreach è per iterare su raccolte che implementano IEnumerable . Lo fa chiamando GetEnumerator sulla raccolta, che restituirà un Enumerator .

Questo Enumeratore ha un metodo e una proprietà:

  • MoveNext ()
  • attuale

Current restituisce l’object che Enumerator è attualmente attivo, MoveNext aggiorna Current all’object successivo.

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

Per questo motivo, la maggior parte delle collezioni può essere attraversata utilizzando un indicizzatore e il costrutto for loop.

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

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 porta l’object ( item.value ) e il suo indice ( item.i ) usando questo overload di Linq’s Select :

il secondo parametro della funzione [all’interno Seleziona] rappresenta l’indice dell’elemento sorgente.

Il new { i, value } sta creando un nuovo object anonimo .

Potrebbe fare qualcosa del genere:

 public static class ForEachExtensions { public static void ForEachWithIndex(this IEnumerable enumerable, Action 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)); } } 

Non sono d’accordo con i commenti sul fatto che un ciclo for è una scelta migliore nella maggior parte dei casi.

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

Ad esempio, se si dispone di un DataReader e si esegue il ciclo di tutti i record utilizzando un foreach esso 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.

(Certo è una buona abitudine chiudere sempre i lettori ma il compilatore non lo prenderà se non lo fai – non puoi garantire di aver chiuso tutti i lettori ma puoi farlo più probabilmente non perderai connessioni ottenendo l’abitudine di usare foreach.)

Potrebbero esserci altri esempi del richiamo implicito del metodo Dispose che è utile.

Risposta letterale: avvertenza, le prestazioni potrebbero non essere buone quanto usare semplicemente un int per tracciare l’indice. Almeno è meglio dell’uso di IndexOf .

Devi solo utilizzare l’overload di indicizzazione di Select per avvolgere ogni elemento della raccolta con un object 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().Select((x, i) => new {x, i})) { Console.WriteLine("{0} {1}", oi, ox); } 

Infine C # 7 ha una syntax decente per ottenere un indice all’interno di un ciclo foreach (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(this IEnumerable self) => self.Select((item, index) => (item, index)); 

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

 //var list = new List { 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 GetEnumerator e quindi GetEnumerator ciclo utilizzando un ciclo for . Tuttavia, il trucco è rendere le condizioni del ciclo listEnumerator.MoveNext() == true .

Poiché il metodo MoveNext di un enumeratore restituisce true se è presente un elemento successivo ed è ansible accedervi, facendo in modo che la condizione di loop interrompa il ciclo quando si esauriscono gli elementi per scorrere l’iterazione.

È ansible avvolgere l’enumeratore originale con un altro che contiene 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 class ForEachHelper .

 public static class ForEachHelper { public sealed class Item { public int Index { get; set; } public T Value { get; set; } public bool IsLast { get; set; } } public static IEnumerable> WithIndex(IEnumerable enumerable) { Item item = null; foreach (T value in enumerable) { Item next = new Item(); 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; } } } 

Ecco una soluzione che mi è venuta in mente 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 ForEach(this IEnumerable enumerable, Action 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; } 

Non c’è niente di sbagliato nell’usare una variabile contatore. In effetti, indipendentemente dal fatto che si usi foreach while o do , una variabile contatore deve essere dichiarata e incrementata.

Quindi usa questo idioma se non sei sicuro di avere una collezione indicizzata in modo appropriato:

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

Altrimenti usalo se sai che la tua collezione indicizzabile è O (1) per l’accesso all’indice (che sarà per Array e probabilmente per List (la documentazione non dice), ma non necessariamente per altri tipi (come 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 "manualmente" far funzionare l' IEnumerator invocando MoveNext() e interrogando Current - foreach sta risparmiando quel particolare fastidio ... se hai bisogno di saltare gli elementi, usa solo un continue nel corpo del loop.

E solo per completezza, a seconda di cosa 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', si ottiene solo un metodo di estensione ForEach() su List e Array ... e non è chiaro che utilizzarlo sia meglio che fare un foreach semplice, dato che si sta ancora eseguendo single- threaded per la più brutta syntax.

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

Questo funzionerebbe per le raccolte che supportano IList .

Usando LINQ, C # 7 e il pacchetto System.ValueTuple NuGet, puoi fare ciò:

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

È ansible utilizzare il normale costrutto foreach ed essere in grado di accedere al valore e all’indice direttamente, non come membro di un object, e mantiene 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 .

Funzionerà solo per un elenco e non per qualsiasi object IEnumerable, ma in LINQ è presente questo:

 IList collection = new List { new Object(), new Object(), new Object(), }; foreach (Object o in collection) { Console.WriteLine(collection.IndexOf(o)); } Console.ReadLine(); 

@ Jonathan Non ho detto che è stata una grande risposta, ho appena detto che stava solo mostrando che era ansible fare ciò che chiedeva 🙂

@Graphain Non mi aspetterei che fosse veloce – non sono del tutto sicuro di come funzioni, potrebbe reiterare l’intera lista ogni volta per trovare un object corrispondente, che sarebbe un helluvalot di confronto.

Detto questo, l’elenco potrebbe mantenere un indice di ogni object insieme al conteggio.

Jonathan sembra avere un’idea migliore, se volesse elaborare?

Sarebbe meglio tenere conto di dove sei nel foreach, più semplice e più adattabile.

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

 static class Extensions { public static IEnumerable< (int, T)> Enumerate( this IEnumerable 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}"); } } } 

Questo è il modo in cui lo faccio, che è bello per la sua semplicità / brevità, ma se stai facendo molto nel corpo del ciclo obj.Value , obj.Value piuttosto veloce.

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

Perché insistere ?!

Il modo più semplice è usare for invece di foreach se stai usando List .

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

O se vuoi usare foreach:

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

puoi usare questo per khow index di ogni Loop:

 myList.indexOf(m) 

Meglio usare la parola chiave continue costruzione sicura come questa

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

L’ho creato in LINQPad :

 var listOfNames = new List(){"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 usare string.join :

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

Se la raccolta è una lista, puoi utilizzare List.IndexOf, come in:

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

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

Posso chiederlo, perché vorresti saperlo?

Sembra che tu stia molto probabilmente facendo una delle tre cose:

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

2) Contando gli oggetti per l’elaborazione post successiva … le raccolte hanno una proprietà Count che potresti utilizzare.

3) Impostazione di una proprietà sull’object in base al suo ordine nel ciclo … anche se potresti facilmente impostarla quando hai aggiunto l’object alla raccolta.

La mia soluzione per 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 { 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)); 

Che ne dici di questo? Si noti che myDelimitedString potrebbe 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; } 

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

Scrive efficacemente un metodo di estensione che avvolge l’iterazione in una class “IteratedItem” (vedi sotto) che consente l’accesso all’indice e all’elemento durante l’iterazione.

 public class IndexedItem { public IndexedItem(int index, TModel item) { Index = index; Item = item; } public int Index { get; private set; } public TModel Item { get; private set; } } 

Tuttavia, mentre questo andrebbe bene in un ambiente non Razor se si sta facendo una singola operazione (cioè una che potrebbe essere fornita come una lambda) non sarà una sostituzione solida della syntax for / foreach in contesti non Razor .

Non ero sicuro di cosa stavi cercando di fare con le informazioni dell’indice basate sulla domanda. Tuttavia, in C #, in genere è ansible adattare il metodo IEnumerable.Select per ottenere l’indice da ciò che si desidera. Per esempio, potrei usare qualcosa del genere per determinare se un valore è pari o dispari.

 string[] names = { "one", "two", "three" }; var oddOrEvenByName = names .Select((name, index) => new KeyValuePair(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.

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

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

Puoi scrivere il tuo loop 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 e extension incapsula la funzionalità Enumerable.Select.

 public struct ValueWithIndex { public readonly T Value; public readonly int Index; public ValueWithIndex(T value, int index) { this.Value = value; this.Index = index; } public static ValueWithIndex Create(T value, int index) { return new ValueWithIndex(value, index); } } public static class ExtensionMethods { public static IEnumerable> GetEnumeratorWithIndex(this IEnumerable enumerable) { return enumerable.Select(ValueWithIndex.Create); } } 

La risposta principale afferma:

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

Anche se questo è vero per la versione C # corrente, questo non è un limite concettuale.

La creazione di una nuova funzionalità in linguaggio C # dalla MS potrebbe risolvere questo problema, insieme al 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 passato a un object IEnumerable e non può risolvere un object IIndexedEnumerable, ma viene richiesto con l’indice var, il compilatore C # può racchiudere l’origine con un object IndexedEnumerable, che aggiunge nel codice per tracciare l’indice.

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

Perché:

  • Foreach sembra più bello, e nelle applicazioni aziendali è raramente un collo di bottiglia delle prestazioni
  • Foreach può essere più efficiente sulla memoria. Avere una pipeline di funzioni invece di convertirle in nuove raccolte ad ogni passaggio. A chi importa se utilizza qualche altro ciclo della CPU, se ci sono meno errori di cache della CPU e meno GC.Collects
  • Richiedere al codificatore di aggiungere il codice di tracciamento dell’indice, rovina la bellezza
  • È abbastanza facile da implementare (grazie MS) ed è retrocompatibile

Mentre la maggior parte delle persone qui non sono MS, questa è una risposta corretta, e puoi fare pressione sulla SM per aggiungere una funzione del genere. Potresti già build il tuo iteratore con una funzione di estensione e usare tuple , ma MS potrebbe cospargere lo zucchero sintattico per evitare la funzione di estensione

A meno che la tua raccolta non possa restituire l’indice dell’object 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 è l’uso di un ciclo for. Qualsiasi altra cosa introduce la complessità del codice, per non parlare della complessità del tempo e dello spazio.

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

Potrebbe essere un caso abbastanza comune, in pratica sto leggendo da un elenco di fonti e creando oggetti basati su di essi in un elenco di destinazione, 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’object nella proprietà Current, tuttavia, poiché sto copiando questi elementi, conosco implicitamente l’indice corrente comunque dalla destinazione corrente. Ovviamente dipende dall’object di destinazione, ma per me era un elenco e molto probabilmente implementerà ICollection.

vale a dire

 var destinationList = new List(); 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 abbastanza spesso per essere degno di nota, penso.

Ad ogni modo, il punto è che a volte c’è una soluzione non ovvia già nella logica che hai …

Ecco un’altra soluzione a questo problema, con l’objective di mantenere la syntax il più vicino ansible a uno standard foreach .

This sort of construct is useful if you are wanting to make your views look nice and clean in MVC. For example instead of writing this the usual way (which is hard to format nicely):

  < %int i=0; foreach (var review in Model.ReviewsList) { %> 

< %:review.Title%>

< %i++; } %>

You could instead write this:

  < %foreach (var review in Model.ReviewsList.WithIndex()) { %> 

< %:review.Title%>

< %} %>

I’ve written some helper methods to enable this:

 public static class LoopHelper { public static int Index() { return (int)HttpContext.Current.Items["LoopHelper_Index"]; } } public static class LoopHelperExtensions { public static IEnumerable WithIndex(this IEnumerable that) { return new EnumerableWithIndex(that); } public class EnumerableWithIndex : IEnumerable { public IEnumerable Enumerable; public EnumerableWithIndex(IEnumerable enumerable) { Enumerable = enumerable; } public IEnumerator GetEnumerator() { for (int i = 0; i < Enumerable.Count(); i++) { HttpContext.Current.Items["LoopHelper_Index"] = i; yield return Enumerable.ElementAt(i); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

In a non-web environment you could use a static instead of HttpContext.Current.Items .

This is essentially a global variable, and so you cannot have more than one WithIndex loop nested, but that is not a major problem in this use case.