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

939

Existe-t-il une construction de langage rare que je n'ai pas rencontrée (comme celles que j'ai apprises récemment, certaines sur Stack Overflow) en C # pour obtenir une valeur représentant l'itération actuelle d'une boucle foreach?

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

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
Matt Mitchell
la source
1
La récupération de cast foreach ne me semble généralement pas plus optimisée que l'utilisation d'un accès basé sur un index sur une collection, bien que dans de nombreux cas, ce sera égal. Le but de foreach est de rendre votre code lisible, mais il ajoute (généralement) une couche d'indirection, qui n'est pas gratuite.
Brian
8
Je dirais que l'objectif principal de foreachest de fournir un mécanisme d'itération commun à toutes les collections, qu'elles soient indexables ( List) ou non ( Dictionary).
Brian Gideon
2
Salut Brian Gideon - tout à fait d'accord (c'était il y a quelques années et j'étais beaucoup moins expérimenté à l'époque). Cependant, bien qu'il Dictionaryne soit pas indexable, une itération de le Dictionarytraverse dans un ordre particulier (c'est-à-dire qu'un énumérateur est indexable par le fait qu'il produit des éléments séquentiellement). En ce sens, nous pourrions dire que nous ne recherchons pas l'index dans la collection, mais plutôt l'index de l'élément énuméré actuel dans l'énumération (c'est-à-dire si nous sommes au premier ou au cinquième ou au dernier élément énuméré).
Matt Mitchell
4
foreach permet également au compilateur de sauter les limites en vérifiant chaque accès au tableau dans le code compilé. L'utilisation de for avec un index permettra au runtime de vérifier si votre accès à l'index est sûr.
IvoTops du
1
Mais c'est faux. Si vous ne modifiez pas la variable d'itération d'une boucle for dans la boucle, le compilateur connaît ses limites et n'a pas besoin de les vérifier à nouveau. C'est un cas si courant que tout compilateur décent l'implémentera.
Jim Balter

Réponses:

552

Le foreachsert à itérer sur les collections qui implémentent IEnumerable. Il le fait en appelant GetEnumeratorla collection, qui retournera un Enumerator.

Cet énumérateur a une méthode et une propriété:

  • MoveNext ()
  • Courant

Currentrenvoie l'objet sur lequel Enumerator se trouve actuellement, MoveNextmet Currentà jour l'objet suivant.

Le concept d'index est étranger au concept d'énumération et ne peut être fait.

Pour cette raison, la plupart des collections peuvent être parcourues à l'aide d'un indexeur et de la construction de boucle for.

Je préfère grandement utiliser une boucle for dans cette situation que de suivre l'index avec une variable locale.

FlySwat
la source
165
"De toute évidence, le concept d'index est étranger au concept d'énumération et ne peut être fait." - C'est absurde, comme le montrent clairement les réponses de David B et bcahill. Un index est une énumération sur une plage, et il n'y a aucune raison pour laquelle on ne peut pas énumérer deux choses en parallèle ... c'est exactement ce que fait la forme d'indexation de Enumerable.Select.
Jim Balter
11
Exemple de code de base:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock
22
@ JimBalter: C'est le premier lien que j'ai lu. Je ne vois pas comment cela soutient votre position. Je ne lance pas non plus de publicités et de mots qui divisent les gens lorsque je réponds. Je cite comme exemple votre utilisation de "non-sens", "confusion extraordinaire", "complètement faux et confus", "j'ai 37 votes positifs", etc. (je pense que votre ego est un peu en danger ici.) Au lieu de cela, je Je voudrais poliment vous demander de ne pas intimider les autres avec votre «argumentum ad verecundiam» et j'aimerais plutôt vous encourager à réfléchir à des moyens de soutenir les gens ici sur StackOverflow. Je dirais que Jon Skeet est un citoyen modèle ici à cet égard.
Pretzel
11
Pretzel: Votre utilisation de Jon Skeet en tant que citoyen modèle est erronée, car il frappera (et dévalorisera) quelqu'un qui n'est pas d'accord avec lui, comme je l'ai vécu. Jim Balter: Certains de vos commentaires étaient trop agressifs et vous auriez pu présenter vos points avec moins de colère et plus d'éducation.
Suncat2000
7
Le point de @Pretzel Jim est que tant que vous pouvez mapper les éléments à une séquence d'entiers, vous pouvez l'indexer. Le fait que la classe elle-même ne stocke pas d'index est hors de propos. En outre, que la liste chaînée n'ont un ordre ne fait que renforcer la position de Jim. Il vous suffit de numéroter chaque élément dans l'ordre. Plus précisément, vous pouvez y parvenir en incrémentant un nombre pendant que vous itérez, ou vous pouvez générer une liste d'entiers de même longueur, puis les compresser (comme dans la fonction de Python ). zip
jpmc26
666

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

Cela vous donne l'élément ( item.value) et son index ( item.i) en utilisant cette surcharge de LINQSelect :

le deuxième paramètre de la fonction [inside Select] représente l'indice de l'élément source.

Le new { i, value }crée un nouvel objet anonyme .

Les allocations de tas peuvent être évitées en utilisant ValueTuplesi vous utilisez C # 7.0 ou version ultérieure:

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

Vous pouvez également éliminer le item.en utilisant la déstructuration automatique:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>
bcahill
la source
9
Cette solution est intéressante dans le cas d'un modèle Razor où la propreté du modèle est une préoccupation de conception non triviale et vous souhaitez également utiliser l'index de chaque élément énuméré. Cependant, gardez à l'esprit que l'allocation d'objet à partir du «wrapping» ajoute des dépenses (en espace et en temps) en plus de l'incrémentation (inévitable) d'un entier.
David Bullock
6
@mjsr La documentation est ici .
Thorkil Holm-Jacobsen
19
Désolé - c'est intelligent, mais est-ce vraiment plus lisible que de créer un index en dehors de foreach et de l'incrémenter à chaque boucle?
2017 à 20h13
13
Avec les versions C # ultérieures, vous utilisez également des tuples, vous aurez donc quelque chose comme ceci: foreach (var (item, i) dans Model.Select ((v, i) => (v, i))) Vous permet de accéder à l'élément et à l'index (i) directement à l'intérieur de la boucle for avec déconstruction de tuple.
Haukman
5
Quelqu'un peut-il m'expliquer pourquoi c'est une bonne réponse (plus de 450 votes positifs au moment de la rédaction)? Pour autant que je sache, c'est plus difficile à comprendre que d'incrémenter simplement un compteur, est donc moins facile à gérer, il utilise plus de mémoire et il est probablement plus lent. Suis-je en train de manquer quelque chose?
Rich N
183

Enfin, C # 7 a une syntaxe décente pour obtenir un index à l'intérieur d'une foreachboucle (c'est-à-dire des 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)); 
user1414213562
la source
9
Cette réponse est sous-estimée, avoir les tuples est beaucoup plus propre
Todd
8
Modifié pour gérer les collections nulles:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Toad
Joli. J'aime beaucoup cette solution.
FranzHuber23
Ceci est la meilleure réponse
w0ns88
2
Il pourrait être utile d'appeler la méthode Enumeratedpour qu'elle soit plus reconnaissable pour les personnes habituées à d' autres langues (et peut-être aussi changer l'ordre des paramètres de tuple). Ce WithIndexn'est pas évident de toute façon.
FernAndr
115

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));
    }
}
Brad Wilson
la source
12
Cela ne résout pas «vraiment» le problème. L'idée est bonne mais elle n'évite pas la variable de comptage supplémentaire
Atmocreations
Cela ne fonctionne pas si nous avons une déclaration de retour dans notre boucle for, si vous changez "ForEachWithIndex" pour cela, alors ce n'est pas générique, mieux vaut écrire une boucle for régulière
Shankar Raju
Votre appel ForEachWithIndex est équivalent à celui-ci en utilisant le Linq Select qui prend une chaîne et un index:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861
95

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

foreachest une construction utile, et non remplaçable par une forboucle en toutes circonstances.

Par exemple, si vous avez un DataReader et parcourez tous les enregistrements à l'aide d'un foreachil appelle automatiquement la méthode Dispose et ferme le lecteur (qui peut ensuite fermer automatiquement la connexion). Ceci est donc plus sûr car il empêche les fuites de connexion même si vous oubliez de fermer le lecteur.

(Bien sûr, il est recommandé de toujours fermer les lecteurs, mais le compilateur ne le détectera pas si vous ne le faites pas - vous ne pouvez pas garantir que vous avez fermé tous les lecteurs, mais vous pouvez augmenter la probabilité de ne pas fuir les connexions en obtenant l'habitude d'utiliser foreach.)

Il peut y avoir d'autres exemples de l'appel implicite de la Disposeméthode utile.

Mike Nelson
la source
2
Merci de l'avoir signalé. Assez subtile. Vous pouvez obtenir plus d'informations sur pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator et msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx .
Mark Meuer
+1. J'écrivais plus en détail sur la foreachdifférence for(et le plus près while) avec Programmers.SE .
Arseni Mourzenko
64

Réponse littérale - avertissement, les performances peuvent ne pas être aussi bonnes que l'utilisation d'un intpour suivre l'index. Au moins, c'est mieux que d'utiliser IndexOf.

Il vous suffit 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 implémente 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);
}
Amy B
la source
3
La seule raison d'utiliser OfType <T> () au lieu de Cast <T> () est si certains éléments de l'énumération peuvent échouer lors d'une conversion explicite. Pour l'objet, ce ne sera jamais le cas.
dahlbyk
13
Bien sûr, sauf pour l'autre raison d'utiliser OfType au lieu de Cast - c'est-à-dire que je n'utilise jamais Cast.
Amy B
36

En utilisant LINQ, C # 7 et le System.ValueTuplepackage NuGet, 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 foreachconstruction régulière et être en mesure d'accéder directement à la valeur et à l'index, pas en tant que membre d'un objet, et ne conserve les deux champs que dans la portée de la boucle. Pour ces raisons, je pense que c'est la meilleure solution si vous pouvez utiliser C # 7 et System.ValueTuple.

Pavel
la source
En quoi est-ce différent de la réponse de user1414213562 ?
Edward Brey
@EdwardBrey Je suppose que non. Cette question a beaucoup de réponses, je l'ai probablement ratée ou je n'ai pas remarqué ce qu'elle faisait parce qu'il a séparé une partie de la logique en une méthode d'extension.
Pavel
1
Ceci est différent car .Select est intégré à partir de LINQ. Vous n'avez pas besoin d'écrire votre propre fonction? Vous devrez cependant avoir VS installer "System.ValueTuple".
Anton
33

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 l'énumérateur en utilisant GetEnumeratorpuis vous boucle en utilisant une forboucle. Cependant, l'astuce consiste à créer l'état de la boucle listEnumerator.MoveNext() == true.

Étant donné que la MoveNextméthode d'un énumérateur renvoie true s'il existe un élément suivant et qu'il est accessible, ce qui fait que la condition de boucle arrête la boucle lorsque nous manquons d'éléments pour itérer.

Gezim
la source
10
Il n'est pas nécessaire de comparer listEnumerator.MoveNext () == true. C'est comme demander à l'ordinateur si vrai == vrai? :) Dites simplement si listEnumerator.MoveNext () {}
Zesty
9
@Zesty, vous avez absolument raison. J'ai senti qu'il est plus lisible de l'ajouter dans ce cas, en particulier pour les personnes qui n'ont pas l'habitude de saisir autre chose que i <blahSize comme condition.
Gezim
@Gezim Cela ne me dérange pas les prédicats ici, mais je comprends que cela ne ressemble pas à un prédicat.
John Dvorak
1
Vous devez jeter l'énumérateur.
Antonín Lejsek
@ AntonínLejsek L'énumérateur n'implémente pas IDisposable.
Edward Brey
27

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

Utilisez donc cet idiome si vous n'êtes pas sûr d'avoir une collection correctement 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 collection indexable est O (1) pour l'accès à l'index (ce sera pour Arrayet probablement pour List<T>(la documentation ne le dit pas), mais pas nécessairement pour d'autres types (comme 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 de faire fonctionner `` manuellement '' le IEnumeratoren invoquant MoveNext()et en l'interrogeant Current- foreachcela vous évite cette peine particulière ... si vous devez sauter des éléments, utilisez simplement un continuedans le corps de la boucle.

Et juste pour être complet, selon ce que vous faisiez avec votre index (les constructions ci-dessus offrent beaucoup de flexibilité), vous pouvez utiliser 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));

Nous utilisons AsParallel()ci-dessus, car c'est déjà 2014, et nous voulons faire bon usage de ces multiples cœurs pour accélérer les choses. De plus, pour LINQ `` séquentiel '', vous obtenez uniquement une ForEach()méthode d'extension List<T>etArray ... et il n'est pas clair que l'utiliser soit mieux que de faire un simple foreach, car vous utilisez toujours un seul thread pour une syntaxe plus laide.

David Bullock
la source
26

Vous pouvez envelopper l'énumérateur d'origine avec un autre qui contient les informations d'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 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;
        }            
    }
}
Brian Gideon
la source
Cela ne retournera pas réellement l'index de l'élément. Au lieu de cela, il renverra l'index à l'intérieur de la liste énumérée, qui ne peut être qu'une sous-liste de la liste, vous donnant ainsi des données précises uniquement lorsque la sous-liste et la liste sont de taille égale. Fondamentalement, chaque fois que la collection contient des objets qui ne sont pas du type demandé, votre index sera incorrect.
Lucas B
7
@Lucas: Non, mais il renverra l'index de l'itération foreach actuelle. Telle était la question.
Brian Gideon
20

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

Code d'origine:

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;
    }
mat3
la source
16

Ajoutez simplement votre propre index. Rester simple.

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

Cela ne fonctionnera que pour une liste et non pour un IEnumerable, mais dans 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 excellente réponse, j'ai juste dit que cela montrait simplement qu'il était possible de faire ce qu'il avait demandé :)

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

Cela dit, List peut conserver un index de chaque objet avec le nombre.

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

Il serait préférable de simplement compter où vous en êtes dans le foreach, plus simple et plus adaptable.

creuset
la source
5
Je ne sais pas sur le lourd vote négatif. Bien sûr, les performances rendent cela prohibitif, mais vous avez répondu à la question!
Matt Mitchell
4
Un autre problème avec cela est que cela ne fonctionne que si les éléments de la liste sont uniques.
CodesInChaos
14

C # 7 nous donne enfin une manière é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}");
        }
    }
}
Paul Mitchell
la source
Oui, et MS devrait étendre le CLR / BCL pour rendre ce genre de chose natif.
Todd
14

Pourquoi foreach?!

La manière la plus simple est d'utiliser for au lieu de foreach si vous utilisez List :

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

Ou si vous souhaitez utiliser foreach:

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

Vous pouvez l'utiliser pour connaître l'index de chaque boucle:

myList.indexOf(m)
Parsa
la source
8
La solution indexOf n'est pas valide pour la liste avec doublons et est également très lente.
tymtam
2
Le problème à éviter est de savoir où vous parcourez l'IEnumerable plusieurs fois, par exemple pour obtenir le nombre d'éléments, puis chaque élément. Cela a des implications lorsque l'IEnumerable est le résultat d'une requête de base de données par exemple.
David Clarke
2
myList.IndexOf () est O (n), donc votre boucle sera O (n ^ 2).
Patrick Beard
9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Cela fonctionnerait pour les collections soutenant IList.

Sachin
la source
68
Deux problèmes: 1) En effet, O(n^2)dans la plupart des implémentations, IndexOfc'est O(n). 2) Cela échoue s'il y a des éléments en double dans la liste.
CodesInChaos
15
Remarque: O (n ^ 2) signifie que cela pourrait être terriblement lent pour une grande collection.
O'Rooney
Grande invention pour utiliser la méthode IndexOf! C'est ce que je cherchais pour obtenir l'index (nombre) dans la boucle foreach! Big Thx
Mitja Bonca
19
Dieu, j'espère que tu n'as pas utilisé ça! :( Il utilise cette variable que vous ne vouliez pas créer - en fait, cela créera n + 1 ints car cette fonction doit en créer une pour retourner également -, et cet index de recherche est beaucoup, beaucoup plus lent que une opération d'incrémentation entière à chaque étape. Pourquoi les gens ne votent-ils pas contre cette réponse?
canahari
13
N'utilisez pas cette réponse, j'ai trouvé la dure vérité mentionnée dans l'un des commentaires. "Cela échoue s'il y a des éléments en double dans la liste." !!!
Bruce
9

C'est comme ça que je le fais, ce qui est bien pour sa simplicité / brièveté, mais si vous faites beaucoup de choses dans le corps de la boucle obj.Value, ça 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);
    ...
}
Ian Henry
la source
8

Cette réponse: faites pression sur l'équipe de langage C # pour une prise en charge linguistique directe.

La réponse principale indique:

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

Bien que cela soit vrai de la version actuelle du langage C # (2020), ce n'est pas une limite conceptuelle CLR / langage, cela peut être fait.

L'équipe de développement du langage Microsoft C # pourrait créer une nouvelle fonctionnalité du langage C #, en ajoutant la prise en charge 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 utilisé et with var indexest présent, le compilateur attend que la collection d'éléments déclare l' IIndexedEnumerableinterface. Si l'interface est absente, le compilateur peut encapsuler polyfill la source avec un objet IndexedEnumerable, qui ajoute le code de suivi de l'index.

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

Plus tard, le CLR peut être mis à jour pour avoir un suivi d'index interne, qui n'est utilisé que si le withmot clé est spécifié et que la source n'implémente pas directementIIndexedEnumerable

Pourquoi:

  • Foreach est plus joli et dans les applications d'entreprise, les boucles foreach sont rarement un goulot d'étranglement des performances
  • Foreach peut être plus efficace sur la mémoire. Avoir un pipeline de fonctions au lieu de convertir à de nouvelles collections à chaque étape. Qui se soucie s'il utilise quelques cycles de processeur supplémentaires lorsqu'il y a moins de défauts de cache de processeur et moins de récupérations de mémoire?
  • Obliger le codeur à ajouter un code de suivi d'index, gâche la beauté
  • Il est assez facile à implémenter (veuillez Microsoft) et est rétrocompatible

Bien que la plupart des gens ici ne soient pas des employés de Microsoft, c'est une bonne réponse, vous pouvez faire pression sur Microsoft pour ajouter une telle fonctionnalité. Vous pouvez déjà créer votre propre itérateur avec une fonction d'extension et utiliser des tuples , mais Microsoft peut saupoudrer le sucre syntaxique pour éviter la fonction d'extension

Todd
la source
Attendez, cette fonctionnalité de langage existe-t-elle déjà ou est-elle proposée pour l'avenir?
Pavel
1
@ Pavel J'ai mis à jour la réponse pour être claire. Cette réponse a été fournie pour contrer la réponse principale qui dit "De toute évidence, le concept d'un indice est étranger au concept d'énumération et ne peut pas être fait."
Todd
6

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

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}
ssaeed
la source
13
Et maintenant, l'algorithme est O (n ^ 2) (sinon pire). Je réfléchirais très attentivement avant d'utiliser cela. C'est aussi un double de la réponse de @crucible
BradleyDotNET
1
@BradleyDotNET a parfaitement raison, n'utilisez pas cette version.
Oskar
1
Soyez prudent avec ça! Si vous avez un élément en double dans votre liste, il obtiendra la position du premier!
Sonhja
5

Mieux vaut utiliser continueune construction sécurisée par mots clés comme celle-ci

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

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 avoir ajouté la structure et la méthode d'extension suivantes.

La méthode struct et d'extension encapsule la fonctionnalité 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);
    }
}
Satisfait
la source
3

Ma solution à 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));
ulrichb
la source
J'utiliserais un structpour la paire (index, élément).
CodesInChaos
3

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

En effet, il écrit une méthode d'extension qui enveloppe l'itération dans une classe "IteratedItem" (voir ci-dessous) permettant l'accès à l'index ainsi qu'à 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, bien que ce soit bien dans un environnement non-Razor si vous effectuez une seule opération (c'est-à-dire une opération qui pourrait être fournie en tant que lambda), cela ne remplacera pas solidement la syntaxe for / foreach dans des contextes non-Razor. .

Matt Mitchell
la source
3

Je ne pense pas que cela devrait être assez efficace, mais cela fonctionne:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}
Bart Calixto
la source
3

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 simplement utiliser string.join:

var joinResult = string.Join(",", listOfNames);
Warren LaFrance
la source
Vous pouvez également utiliser le string.join en c # Par exemple: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance
1
Quelqu'un peut-il m'expliquer quelle est cette chose O (n * n)?
Axel
@Axel, cela signifie essentiellement que les opérations nécessaires pour calculer le résultat augmentent de façon quadratique, c'est-à-dire que s'il y a des néléments, les opérations le sont n * nou au ncarré. en.wikipedia.org/wiki/…
Richard Hansell
2

Je ne crois pas qu'il existe un moyen d'obtenir la valeur de l'itération actuelle d'une boucle foreach. Se compter, semble être le meilleur moyen.

Puis-je vous demander pourquoi vous voudriez savoir?

Il semble que vous feriez le plus probablement l'une des trois choses suivantes:

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

2) Comptage des objets pour un post-traitement ultérieur ... les collections ont une propriété Count que vous pouvez utiliser.

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

Bryansh
la source
4) Le cas que j'ai frappé plusieurs fois est quelque chose de différent qui doit être fait lors du premier ou du dernier passage - dites une liste d'objets que vous allez imprimer et vous avez besoin de virgules entre les éléments mais pas après le dernier élément.
Loren Pechtel
2

À moins que votre collection ne puisse retourner l'index de l'objet via une méthode, la seule façon est d'utiliser un compteur comme dans votre exemple.

Cependant, lorsque vous travaillez avec des index, la seule réponse raisonnable au problème consiste à utiliser une boucle for. Tout autre élément introduit la complexité du code, sans parler de la complexité du temps et de l'espace.

Joseph Daigle
la source
2

Que diriez-vous quelque chose comme ça? Notez que myDelimitedString peut être null 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;
}
Tours Matt
la source
Plusieurs problèmes ici. A) attelle supplémentaire. B) chaîne concat + = par itération de boucle.
enorl76
2

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

Cela pourrait être un cas assez courant, en gros, je lis à partir d'une liste source et crée 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 vouloir retourner la ligne de tout Erreur. À première vue, je veux obtenir l'index dans l'énumérateur de l'objet à la propriété Current, cependant, comme je copie ces éléments, je connais implicitement l'index actuel de toute façon à partir de la destination actuelle. Évidemment, cela dépend de votre objet de destination, mais pour moi, c'était une liste, et très probablement il 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 être mentionné, je pense.

Quoi qu'il en soit, le fait est qu'il existe parfois une solution non évidente déjà dans la logique que vous avez ...

nicodemus13
la source
2

Je n'étais pas sûr de ce que vous essayiez de faire avec les informations d'index basées sur la question. Cependant, en C #, vous pouvez généralement adapter la méthode IEnumerable.Select pour extraire l'index de tout ce que vous voulez. Par exemple, je pourrais utiliser quelque chose comme ça pour savoir si une valeur est impaire ou paire.

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

Cela vous donnerait un dictionnaire par nom pour savoir si l'élément était impair (1) ou même (0) dans la liste.

Kasey Speakman
la source