Question Comment obtenez-vous l'indice de l'itération en cours d'une boucle foreach?


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

Par exemple, je fais actuellement quelque chose comme ça en fonction des circonstances:

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

669
2017-09-04 01:38


origine


Réponses:


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

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

  • MoveNext ()
  • Actuel

Current renvoie l'objet sur lequel l'énumérateur est actuellement activé 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 traversées en utilisant un indexeur et la construction for loop.

Je préfère grandement utiliser une boucle for dans cette situation par rapport au suivi de l'index avec une variable locale.


433
2017-09-04 01:46



Ian Mercer a posté 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 obtient l'article (item.value) et son index (item.i) en utilisant cette surcharge de Linq Select:

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

le new { i, value } crée une nouvelle objet anonyme.


441
2017-07-11 16:52



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

98
2017-09-04 01:49



Je suis en désaccord avec les commentaires qu'un for La boucle est un meilleur choix dans la plupart des cas.

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

Par exemple, si vous avez un DataReader et boucle à travers tous les enregistrements en utilisant un foreach il appelle automatiquement le Disposer méthode et ferme le lecteur (qui peut ensuite fermer la connexion automatiquement). 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 pourra pas l'attraper si vous ne le faites pas. Vous ne pouvez pas garantir que vous avez fermé tous les lecteurs, mais vous pouvez augmenter le risque de fuites de connexion. dans l'habitude d'utiliser foreach.)

Il peut y avoir d'autres exemples de l'appel implicite du Dispose méthode étant utile.


77
2018-06-25 23:49



Réponse littérale - avertissement, la performance peut ne pas être aussi bonne que d'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 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);
}

54
2017-09-16 21:50



Enfin C # 7 a une syntaxe décente pour obtenir un index à l'intérieur d'un foreach boucle (c'est-à-dire 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)); 

40
2017-10-12 11:12



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 GetEnumerator puis vous bouclez en utilisant un forboucle. Cependant, l'astuce consiste à rendre l'état de la boucle listEnumerator.MoveNext() == true.

Depuis le MoveNext La méthode d'un énumérateur renvoie true s'il y a un élément suivant et que l'on peut y accéder, ce qui fait que la condition de boucle fait s'arrêter la boucle lorsque nous manquons d'éléments pour itérer.


32
2017-10-07 22:49



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 pour le ForEachHelper classe.

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

21
2017-07-20 19:15