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




if demander (25)

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

Answers

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

Voici une solution que je viens de proposer 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;
    }

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 GetEnumerator utilisant une boucle for . Cependant, l'astuce consiste à rendre la condition de la boucle listEnumerator.MoveNext() == true .

Étant donné que la méthode MoveNext d'un énumérateur renvoie true s'il y a un élément suivant et qu'il est accessible, la condition de boucle fait que la boucle s'arrête lorsque nous manquons d'éléments à parcourir.


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

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

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

Le foreach est pour l'itération sur les collections qui implémentent IEnumerable . Il le fait en appelant GetEnumerator sur la collection, ce qui retournera un Enumerator .

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

  • MoveNext ()
  • Actuel

Current renvoie l'objet sur lequel Enumerator est actuellement MoveNext , MoveNext met à jour Current vers 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.


La réponse principale indique:

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

Bien que cela soit vrai pour la version C # actuelle, il ne s'agit pas d'une limite conceptuelle.

La création d'une nouvelle fonctionnalité de langage C # par MS pourrait résoudre cela, avec 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, le compilateur C # peut envelopper la source avec un objet IndexedEnumerable, 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 semble plus agréable, et dans les applications d'entreprise est rarement un goulot d'étranglement de la performance
  • 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 un peu plus de cycles de CPU, s'il y a moins de défauts de cache CPU et moins de GC.Collects
  • Demander au codeur d'ajouter un code de suivi d'index, gâte la beauté
  • Il est assez facile à mettre en œuvre (merci MS) et est rétrocompatible

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


Mieux vaut utiliser le mot-clé continue la construction sûre comme ceci

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

Réponse littérale - avertissement, les performances peuvent ne pas être aussi bonnes que d'utiliser un int pour suivre l'index. 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);
}

En utilisant LINQ, C # 7 et le System.ValueTuple System.ValueTuple 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 construction foreach standard et être en mesure d'accéder directement à la valeur et à l'index, et non en tant que membre d'un objet, et conserver les deux champs uniquement dans la portée de la boucle. Pour ces raisons, je crois que c'est la meilleure solution si vous êtes en mesure d'utiliser C # 7 et System.ValueTuple .


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 ceci pour savoir si une valeur est pair ou impair.

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 de si l'article était impair (1) ou même (0) dans la liste.


Pour l'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 )

Effectivement, 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-rasoir si vous faites une seule opération (ie une qui pourrait être fournie comme un lambda) cela ne va pas être un remplacement solide de la syntaxe for / foreach dans des contextes non-rasoir .


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

Puis-je demander, pourquoi voudriez-vous savoir?

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

1) Obtenir 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 définir cela lorsque vous avez ajouté l'objet à la collection.


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

Here is another solution to this problem, with a focus on keeping the syntax as close to a standard foreach as possible.

Ce type de construction est utile si vous voulez que vos vues soient belles et propres dans MVC. Par exemple, au lieu d'écrire ceci de la manière habituelle (ce qui est difficile à mettre en forme):

 <%int i=0;
 foreach (var review in Model.ReviewsList) { %>
    <div id="review_<%=i%>">
        <h3><%:review.Title%></h3>                      
    </div>
    <%i++;
 } %>

Vous pourriez plutôt écrire ceci:

 <%foreach (var review in Model.ReviewsList.WithIndex()) { %>
    <div id="review_<%=LoopHelper.Index()%>">
        <h3><%:review.Title%></h3>                      
    </div>
 <%} %>

J'ai écrit quelques méthodes d'aide pour activer ceci:

public static class LoopHelper {
    public static int Index() {
        return (int)HttpContext.Current.Items["LoopHelper_Index"];
    }       
}

public static class LoopHelperExtensions {
    public static IEnumerable<T> WithIndex<T>(this IEnumerable<T> that) {
        return new EnumerableWithIndex<T>(that);
    }

    public class EnumerableWithIndex<T> : IEnumerable<T> {
        public IEnumerable<T> Enumerable;

        public EnumerableWithIndex(IEnumerable<T> enumerable) {
            Enumerable = enumerable;
        }

        public IEnumerator<T> 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();
        }
    }

Dans un environnement non Web, vous pouvez utiliser un staticau lieu de HttpContext.Current.Items.

Il s'agit essentiellement d'une variable globale. Vous ne pouvez donc pas imbriquer plus d'une boucle WithIndex, mais ce n'est pas un problème majeur dans ce cas d'utilisation.


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

Utilisez donc cette expression 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 cette option si vous savez que votre collection indexable est O (1) pour l'accès à l'index (ce sera pour Array et probablement pour List<T> (la documentation ne le dit pas), mais pas nécessairement pour les autres types (tels que 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 IEnumerator en MoveNext() et en interrogeant Current - foreach vous évite ce problème particulier ... si vous avez besoin d'ignorer des éléments, utilisez simplement un continue dans le corps de la boucle.

Et juste pour l'exhaustivité, en fonction de 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 nous sommes déjà en 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 seulement une méthode d'extension ForEach() sur List<T> et Array ... et il n'est pas clair que son utilisation soit meilleure que de faire une simple foreach , puisque vous utilisez toujours single- enfilé pour une syntaxe plus laide.


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

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

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


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

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

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


Je ne pense pas que cela devrait être très efficace, mais cela fonctionne:

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

Je ne suis pas d'accord avec les commentaires qu'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 dans toutes les circonstances.

Par exemple, si vous avez un DataReader et parcourez tous les enregistrements à l'aide d'un foreach il 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 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 de la méthode Dispose utile.


À moins que votre collection puisse renvoyer l'index de l'objet via une méthode quelconque, 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 le reste introduit la complexité du code, sans parler de la complexité du temps et de l'espace.


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

Je ne sais pas si c'est encore nécessaire. Mais la solution suivante devrait fonctionner avec les itérateurs et ne nécessite pas de count .

<?php

foreach_first_last(array(), function ($key, $value, $step, $first, $last) {
    echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
});

foreach_first_last(array('aa'), function ($key, $value, $step, $first, $last) {
    echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
});
echo PHP_EOL;

foreach_first_last(array('aa', 'bb', 'cc'), function ($key, $value, $step, $first, $last) {
    echo intval($first), ' ', intval($last), ' ', $step, ' ', $value, PHP_EOL;
});
echo PHP_EOL;

function foreach_first_last($array, $cb)
{
    $next = false;
    $current = false;
    reset($array);
    for ($step = 0; true; ++$step) {
        $current = $next;
        $next = each($array);
        $last = ($next === false || $next === null);
        if ($step > 0) {
            $first = $step == 1;
            list ($key, $value) = $current;
            if (call_user_func($cb, $key, $value, $step, $first, $last) === false) {
                break;
            }
        }
        if ($last) {
            break;
        }
    }
}






c# foreach