valeur - while c#




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

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

À 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.


C # 7 nous donne finalement une manière élégante de faire ceci:

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

Cela ne fonctionnera que pour une liste et pas pour 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 bonne réponse, j'ai juste dit que c'était juste de montrer qu'il était possible de faire ce qu'il demandait :)

@Graphain Je ne m'attendrais pas à être rapide - je ne suis pas tout à fait sûr 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 comparés.

Cela dit, List peut garder un index de chaque objet avec le compte.

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

Il vaudrait mieux que vous gardiez un compte de ce que vous faites dans la foreach, plus simple et plus adaptable.


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 .


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

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 .


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

J'ai juste eu ce problème, mais en réfléchissant au problème dans mon cas, j'ai trouvé la meilleure solution, sans rapport avec la solution attendue.

Cela peut être un cas assez commun, je lis d'une liste de sources et crée des objets basés sur eux dans une liste de destination, cependant, je dois vérifier si les éléments sources sont d'abord valides et que je veux retourner la ligne de n'importe quelle liste. Erreur. At first-glance, I want to get the index into the enumerator of the object at the Current property, however, as I am copying these elements, I implicitly know the current index anyway from the current destination. Obviously it depends on your destination object, but for me it was a List, and most likely it will implement 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]});
  }
}

Not always applicable, but often enough to be worth mentioning, I think.

Anyway, the point being that sometimes there is a non-obvious solution already in the logic you have...


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.


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

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

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


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.


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

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 .


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

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

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

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

Pourquoi foreach?!

Le moyen le 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 voulez utiliser foreach:

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

vous pouvez l'utiliser pour indexer khow de chaque boucle:

myList.indexOf(m)

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.





foreach