Comment obtenir l'index de l'itération courante d'une boucle foreach?

y a-t-il une construction de langage rare que je n'ai pas rencontrée (comme celles que j'ai apprises récemment, certaines Sur le débordement de la pile) dans C# pour obtenir une valeur représentant l'itération actuelle d'une boucle de foreach?

par exemple, je fais actuellement quelque chose comme ça selon les circonstances:

int i=0;
foreach (Object o in collection)
{
    // ...
    i++;
}
699
demandé sur user7116 2008-09-04 05:38:39

30 réponses

le foreach est pour itérer sur les collections qui mettent en œuvre IEnumerable . Il le fait en appelant GetEnumerator sur la collection, qui retournera un Enumerator .

ce recenseur a une méthode et une propriété:

  • MoveNext ()
  • courant

Current retourne le objet Énumérateur est actuellement sur, MoveNext mises à jour Current à l'objet suivant.

de toute évidence, le concept d'indice est étranger au concept d'énumération et ne peut être fait.

pour cette raison, la plupart des collections peuvent être parcourues en utilisant un indexeur et la construction de boucle.

je préfère fortement utiliser une boucle for dans cette situation par rapport au suivi de l'indice avec une variable locale.

448
répondu FlySwat 2014-03-08 14:54:35

Ian Mercer a publié une solution similaire à celle-ci sur le blog de Phil Haack :

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

vous obtenez l'article ( item.value ) et son index ( item.i ) en utilisant cette surcharge de Linq Select :

le second paramètre de la fonction [inside Select] représente l'index de l'élément source.

le new { i, value } est création d'un nouvel objet anonyme .

460
répondu bcahill 2017-07-24 11:42:39

pourrait faire quelque chose comme ceci:

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));
    }
}
101
répondu Brad Wilson 2008-09-04 01:49:28

Je ne suis pas d'accord avec les commentaires selon lesquels une boucle for est un meilleur choix dans la plupart des cas.

foreach est une construction utile, et non remplaçable par une boucle for en toutes circonstances.

par exemple, si vous avez un DataReader et boucle à travers tous les enregistrements en utilisant un foreach il appelle automatiquement le éliminez méthode et ferme le lecteur (qui peut alors fermer le automatiquement la connexion). Ceci est donc plus sûr car il prévient les fuites de connexion Même si vous oubliez de fermer le lecteur.

(bien sûr c'est une bonne pratique de toujours fermer les lecteurs, mais le compilateur ne va pas l'attraper si vous ne le faites pas - vous ne pouvez pas garantir que vous avez fermé tous les lecteurs, mais vous pouvez le rendre plus probable vous ne fuirez pas les connexions en prenant l'habitude d'utiliser foreach.)

il peut y avoir d'autres exemples de l'appel implicite du La méthode Dispose est utile.

78
répondu mike nelson 2012-07-25 10:07:34

réponse littérale -- avertissement, la performance peut ne pas être aussi bonne que de simplement utiliser un int pour suivre l'indice. Au moins, c'est mieux que d'utiliser IndexOf .

vous avez juste besoin d'utiliser la surcharge d'indexation de Select pour envelopper chaque élément de la collection avec un objet anonyme qui connaît l'index. Cela peut être fait contre tout ce qui met en œuvre IEN innombrables.

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);
}
56
répondu Amy B 2013-11-27 07:44:42

enfin C#7 a une syntaxe décente pour obtenir un index à l'intérieur d'une boucle foreach (I. E. tuples):

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

une petite méthode d'extension serait nécessaire:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 
48
répondu user1414213562 2016-10-19 16:59:36

en utilisant la réponse de @FlySwat, j'ai trouvé cette solution:

//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
}

vous obtenez le recenseur en utilisant GetEnumerator et ensuite vous bouclez en utilisant une boucle for . Cependant, le truc est de rendre la condition de la boucle listEnumerator.MoveNext() == true .

puisque la méthode MoveNext d'un recenseur retourne true s'il y a un élément suivant et qu'il peut être accédé, ce qui fait que la condition de boucle fait s'arrêter la boucle lorsque nous manquons d'éléments pour itérer plus.

32
répondu Gezim 2013-10-07 22:49:52

vous pouvez envelopper le recenseur d'origine avec un autre qui contient les informations de l'index.

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

voici le code de la classe ForEachHelper .

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;
        }            
    }
}
22
répondu Brian Gideon 2010-07-23 13:02:41

Voici une solution que je viens de trouver pour ce problème

code Original:

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

code mis à jour

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

Méthode D'Extension:

    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
répondu mat3 2009-12-31 11:31:41

il n'y a rien de mal à utiliser une variable de compteur. En fait, si vous utilisez for , foreach while ou do , une variable de compteur doit quelque part être déclarée et incrémentée.

utilisez donc cet idiome si vous n'êtes pas certain d'avoir une collection convenablement indexée:

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

sinon utilisez celui-ci si vous savez que votre indexable collection est O(1) pour accès index (qui sera pour Array et probablement pour List<T> (la documentation ne dit pas), mais pas nécessairement pour d'autres types (tels que 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'
}

il ne devrait jamais être nécessaire d'utiliser" manuellement "le IEnumerator en invoquant MoveNext() et en interrogeant Current - foreach vous épargne cette peine particulière ... si vous avez besoin d'ignorer les éléments, il suffit d'utiliser un continue dans le corps de la boucle.

et juste pour l'exhaustivité, selon ce que vous étiez faire avec votre index (les constructions ci-dessus offrent beaucoup de flexibilité), vous pourriez utiliser Linq parallèle:

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

nous utilisons AsParallel() ci-dessus, parce que c'est déjà 2014, et nous voulons faire bon usage de ces noyaux multiples pour accélérer les choses. En outre, pour' sequential 'LINQ, vous obtenez seulement une ForEach() méthode d'extension sur List<T> et Array ... et il n'est pas clair que l'utiliser est mieux que de faire un simple foreach , puisque vous utilisez encore un thread simple pour une syntaxe plus moche.

15
répondu David Bullock 2017-05-23 11:33:27

en utilisant LINQ, C# 7, et le paquet NuGet System.ValueTuple , vous pouvez faire ceci:

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

vous pouvez utiliser la construction régulière foreach et être en mesure d'accéder à la valeur et l'index directement, pas en tant que membre d'un objet, et garde les deux champs seulement dans la portée de la boucle. Pour ces raisons, je pense que c'est la meilleure solution si vous êtes en mesure d'utiliser C# 7 et System.ValueTuple .

14
répondu Pavel 2017-12-18 15:57:38
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

cela fonctionnerait pour les collections supportant IList .

12
répondu Sachin 2010-01-02 03:50:06

ça ne marchera que pour une liste et pas N'importe laquelle, mais à LINQ il y a ceci:

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 je n'ai pas dit que c'était une grande réponse, j'ai juste dit que c'était juste montrer qu'il était possible de faire ce qu'il a demandé :)

@Graphain Je ne m'attendrais pas à ce que ce soit rapide - Je ne suis pas entièrement sûr de comment cela fonctionne, il pourrait réitérer à travers la liste entière à chaque fois pour trouver un objet correspondant, qui serait un helluvalot de comparaisons.

cela dit, List pourrait garder un index de chaque objet avec le compte.

Jonathan semble avoir une meilleure idée, s'il veut élaborer?

Il serait préférable de simplement tenir un compte de l'endroit où vous êtes dans le foreach bien, plus simple, et plus adaptable.

9
répondu crucible 2008-09-04 02:51:31

C'est ainsi que je le fais, ce qui est bien pour sa simplicité/brièveté, mais si vous faites beaucoup dans le corps de boucle obj.Value , il va vieillir assez rapidement.

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
répondu Ian Henry 2015-07-28 11:52:49

C# 7 nous donne enfin une façon élégante de le faire:

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}");
        }
    }
}
8
répondu Paul Mitchell 2017-07-21 13:52:17

pourquoi foreach ?!

la manière la plus simple est d'utiliser pour au lieu de si vous utilisez la liste .

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

ou si vous voulez utiliser foreach:

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

vous pouvez utiliser cet indice de khow de chaque boucle:

myList.indexOf(m)
7
répondu Parsa 2016-05-26 21:41:22

mieux utiliser le mot-clé continue construction sûre comme celle-ci

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}
6
répondu user426810 2013-07-26 14:49:57

Je ne pense pas que cela devrait être tout à fait efficace, mais cela fonctionne:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}
4
répondu Bart Calixto 2015-07-28 11:54:51

j'ai construit ceci dans 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.

vous pouvez également utiliser string.join :

var joinResult = string.Join(",", listOfNames);
4
répondu Warren LaFrance 2015-07-28 11:58:54

si la collection est une liste, vous pouvez utiliser List.IndexOf, comme dans:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}
4
répondu ssaeed 2015-11-04 03:06:12

je ne crois pas qu'il existe un moyen d'obtenir la valeur de l'itération courante de la boucle foreach. Compter sur soi-même semble être le meilleur moyen.

puis-je vous demander, pourquoi vous voulez savoir?

il semble que vous feriez très probablement l'une des trois choses:

1) l'objet de la collection, mais dans ce cas, vous l'avez déjà.

2) compter les objets pour post ultérieur traitement...les collections ont une propriété de Comte que vous pourriez utiliser.

3) Définir une propriété sur l'objet en fonction de son ordre dans la boucle...bien que vous pourriez facilement être que lorsque vous avez ajouté l'objet à la collection.

3
répondu bryansh 2008-09-04 01:53:50

ma solution pour ce problème est une méthode d'extension WithIndex() ,

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

utilisez-le comme

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));
3
répondu ulrichb 2010-09-14 21:10:53

Que Diriez-vous de quelque chose comme ça? Notez que myDelimitedString peut être nul si myEnumerable est vide.

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;
}
3
répondu Matt Towers 2010-10-29 19:27:25

pour intérêt, Phil Haack vient d'écrire un exemple de ceci dans le contexte d'un délégué de Razor Templated ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

écrit effectivement une méthode d'extension qui enveloppe l'itération dans une classe" IteratedItem " (voir ci-dessous) permettant l'accès à l'index ainsi que l'élément pendant l'itération.

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

cependant, alors que ce serait bien dans un environnement non-Razor si vous effectuez une seule opération (C.-à-d. une opération qui pourrait être fournie comme lambda) ce ne sera pas un remplacement solide de la syntaxe for/foreach dans des contextes non-Razor.

3
répondu Matt Mitchell 2011-04-18 23:25:15

Je n'étais pas sûr de ce que vous essayiez de faire avec l'information index basée sur la question. Cependant, en C#, vous pouvez généralement adapter le IEnumerable.Sélectionnez la méthode pour obtenir l'index de ce que vous voulez. Par exemple, je pourrais utiliser quelque chose comme ceci pour savoir si une valeur est impaire ou même.

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

ceci vous donnerait un dictionnaire par le nom de si l'article était Impair (1) ou même (0) dans la liste.

3
répondu Kasey Speakman 2011-09-14 19:03:54

Vous pouvez écrire votre boucle comme ceci:

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

après l'ajout de la méthode struct et extension suivante.

la méthode struct et extension encapsule Enumerable.Sélectionnez fonctionnalité.

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
répondu Satisfied 2016-08-23 15:48:03

la réponse principale est:

" évidemment, le concept d'un indice est étranger au concept d'énumération, et ne peut pas être fait."

bien que cela soit vrai de la version C# actuelle, ce n'est pas une limite conceptuelle.

la création D'une nouvelle fonctionnalité C# language par MS pourrait résoudre ce problème, ainsi que le support d'une nouvelle Interface 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);
}

si foreach est passé un IEnumerable et ne peut pas résoudre un IIndexedEnumerable, mais il est demandé avec var index, puis le compilateur C# peut envelopper la source avec un IndexedEnumerable objet, qui ajoute dans le code pour le suivi de l'index.

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

pourquoi:

  • Foreach ressemble plus belle, et dans les applications d'entreprise est rarement un goulot d'étranglement des performances
  • Foreach peut être plus efficace sur la mémoire. Ayant un pipeline de fonctions au lieu de la conversion aux nouvelles collections à chaque étape. Qui se soucie si elle utilise un peu plus de cycles CPU, s'il y a moins de défauts de cache CPU et moins de GC.Collects
  • exiger du codeur d'ajouter le code de suivi d'index, gâche la beauté
  • c'est assez facile à mettre en œuvre (merci MS) et est rétro compatible

alors que la plupart des gens ici ne sont pas des MS, c'est une réponse correcte, et vous pouvez faire pression sur MS pour ajouter une telle fonctionnalité. Vous pouvez déjà construisez votre propre itérateur avec une fonction d'extension et utilisez tuples , mais MS pourrait saupoudrer le sucre syntaxique pour éviter la fonction d'extension

3
répondu Todd 2017-09-20 01:16:45

sauf si votre collection peut retourner l'index de l'objet par une méthode quelconque, la seule façon est d'utiliser un compteur comme dans votre exemple.

Cependant, en travaillant avec des index, la seule réponse raisonnable au problème est d'utiliser un pour boucle. Tout le reste introduit la complexité du code, sans parler de la complexité du temps et de l'espace.

2
répondu Joseph Daigle 2008-09-04 02:50:16

je viens d'avoir ce problème, mais penser au problème dans mon cas a donné la meilleure solution, sans rapport avec la solution attendue.

cela pourrait être un cas assez courant, fondamentalement, je suis en train de lire à partir d'une liste de sources et de créer des objets basés sur eux dans une liste de destination, cependant, je dois vérifier si les éléments source sont valides en premier et je veux retourner la rangée de toute erreur. À première vue, je veux obtenir l'index dans l'énumérateur de l'objet à la Propriété courante, cependant, comme je copie ces éléments, je connais implicitement l'index courant de toute façon de la destination courante. Cela dépend évidemment de votre objet destination, mais pour moi c'était une liste, et très probablement cela implémentera ICollection.

c'est à 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]});
  }
}

pas toujours applicable, mais assez souvent pour mériter d'être mentionné, je pense.

de toute façon, le fait est que parfois il ya une non-évidence solution déjà dans la logique que vous avez...

2
répondu nicodemus13 2011-03-23 12:34:52

cela ne répond pas à votre question spécifique, mais il vous fournit une solution à votre problème: utilisez une boucle for pour exécuter à travers la collection d'objets. ensuite, vous aurez l'indice actuel vous travaillez sur.

// Untested
for (int i = 0; i < collection.Count; i++)
{
    Console.WriteLine("My index is " + i);
}
1
répondu BKSpurgeon 2016-04-02 02:11:08