web-dev-qa-db-fra.com

Possible de parcourir en arrière à travers une foreach?

Je sais que je pourrais utiliser une instruction for et obtenir le même effet, mais puis-je parcourir en arrière une boucle foreach en C #?

101
JL.

Lorsque vous travaillez avec une liste (indexation directe), vous ne pouvez pas le faire aussi efficacement qu'avec une boucle for.

Edit: ce qui signifie généralement que, lorsque vous êtes capable d'utiliser une boucle for, il s'agit probablement de la méthode correcte pour cette tâche. De plus, pour autant que foreach soit implémenté dans l’ordre, la construction elle-même est construite pour exprimer des boucles indépendantes des index des éléments et de l’ordre des itérations, ce qui est particulièrement important dans la programmation parallèle . Il est à mon avis que l'itération basée sur l'ordre ne doit pas utiliser foreach pour la mise en boucle.

70
Sam Harwell

Si vous êtes sur .NET 3.5, vous pouvez faire ceci:

IEnumerable<int> enumerableThing = ...;
foreach (var x in enumerableThing.Reverse())

Ce n’est pas très efficace, car il doit en principe passer par l’énumérateur pour tout mettre sur une pile, puis tout repasser dans l’ordre inverse.

Si vous avez une collection indexable directement (par exemple, IList), vous devez absolument utiliser une boucle for à la place.

Si vous utilisez .NET 2.0 et que vous ne pouvez pas utiliser de boucle for (c’est-à-dire que vous n’avez qu’un IEnumerable), il vous suffira d’écrire votre propre fonction Reverse. Cela devrait fonctionner:

static IEnumerable<T> Reverse<T>(IEnumerable<T> input)
{
    return new Stack<T>(input);
}

Cela repose sur un comportement qui n’est peut-être pas si évident. Lorsque vous transmettez un IEnumerable au constructeur de la pile, celui-ci effectue une itération et transfère les éléments dans la pile. Lorsque vous parcourez ensuite la pile, les éléments sont affichés dans l'ordre inverse.

Ceci et la méthode d'extension .NET 3.5 Reverse() vont évidemment exploser si vous lui donnez un IEnumerable qui n'arrête jamais de renvoyer des éléments.

124
Matt Howells

Comme 280Z28 le dit, pour un IList<T>, vous pouvez simplement utiliser l'index. Vous pouvez cacher ceci dans une méthode d'extension:

public static IEnumerable<T> FastReverse<T>(this IList<T> items)
{
    for (int i = items.Count-1; i >= 0; i--)
    {
        yield return items[i];
    }
}

Ce sera plus rapide que Enumerable.Reverse() qui met en mémoire tampon toutes les données en premier. (Je ne crois pas que Reverse a été optimisé de la même manière que Count().) Notez que cette mise en mémoire tampon signifie que les données sont lues complètement lorsque vous démarrez pour la première fois, alors que FastReverse "voit" les modifications apportées à la liste répéter. (Cela se brisera également si vous supprimez plusieurs éléments entre les itérations.)

Pour les séquences générales, il n’ya aucun moyen de faire une itération inverse: la séquence peut être infinie, par exemple:

public static IEnumerable<T> GetStringsOfIncreasingSize()
{
    string ret = "";
    while (true)
    {
        yield return ret;
        ret = ret + "x";
    }
}

Qu'attendriez-vous de ce qui se passerait si vous tentiez d'itérer cela en sens inverse?

46
Jon Skeet

Avant d'utiliser 'foreach' pour l'itération, inversez la liste à l'aide de la méthode 'reverse':

    myList.Reverse();
    foreach( List listItem in myList)
    {
       Console.WriteLine(listItem);
    }
6
Prem

Si vous utilisez une liste <T>, vous pouvez également utiliser ce code:

List<string> list = new List<string>();
list.Add("1");
list.Add("2");
list.Add("3");
list.Reverse();

C'est une méthode qui écrit la liste inverse en elle-même.

Maintenant le foreach:

foreach(string s in list)
{
    Console.WriteLine(s);
}

La sortie est:

3
2
1
4
th3s0urc3

Parfois, vous n'avez pas le luxe d'indexer, ou peut-être souhaitez-vous inverser les résultats d'une requête Linq, ou peut-être que vous ne souhaitez pas modifier la collection source, si l'une d'entre elles est vraie, Linq peut vous aider.

Une méthode d'extension Linq utilisant des types anonymes avec Linq Select pour fournir une clé de tri pour Linq OrderByDescending;

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Usage:

    var eable = new[]{ "a", "b", "c" };

    foreach(var o in eable.Invert())
    {
        Console.WriteLine(o);
    }

    // "c", "b", "a"

Il s'appelle "Invert" car il est synonyme de "Reverse" et permet de lever l'ambiguïté de la mise en oeuvre de List Reverse.

Il est également possible d'inverser certaines plages d'une collection, car Int32.MinValue et Int32.MaxValue sont hors de la plage de tout type d'index de collection. Nous pouvons les exploiter pour le processus de commande; Si un index d’élément est inférieur à la plage donnée, il se voit attribuer Int32.MaxValue afin que son ordre ne change pas lors de l’utilisation de OrderByDescending. apparaissent à la fin du processus de commande. Tous les éléments de la plage donnée se voient attribuer leur index normal et sont inversés en conséquence.

    public static IEnumerable<T> Invert<T>(this IEnumerable<T> source, int index, int count)
    {
        var transform = source.Select(
            (o, i) => new
            {
                Index = i < index ? Int32.MaxValue : i >= index + count ? Int32.MinValue : i,
                Object = o
            });

        return transform.OrderByDescending(o => o.Index)
                        .Select(o => o.Object);
    }

Usage:

    var eable = new[]{ "a", "b", "c", "d" };

    foreach(var o in eable.Invert(1, 2))
    {
        Console.WriteLine(o);
    }

    // "a", "c", "b", "d"

Je ne suis pas sûr des résultats de performance de ces implémentations Linq par rapport à l'utilisation d'une liste temporaire pour envelopper une collection en vue de son inversion.


Au moment de la rédaction de ce document, je ne connaissais pas l'implémentation inverse de Linq, mais c'était amusant de travailler avec cela. https://msdn.Microsoft.com/en-us/library/vstudio/bb358497(v=vs.100).aspx

4
Vorspire

Il est possible si vous pouvez modifier le code de collection qui implémente IEnumerable ou IEnumerable (par exemple, votre propre implémentation de IList).

Créez un Iterator faisant ce travail pour vous, par exemple, comme dans l'implémentation suivante via l'interface IEnumerable (en supposant que 'items' est un champ List dans cet exemple):

public IEnumerator<TObject> GetEnumerator()
{
    for (var i = items.Count - 1; i >= 0; i--)
    { 
        yield return items[i];
    }
}

IEnumerator IEnumerable.GetEnumerator()
{
    return GetEnumerator();
}

Pour cette raison, votre liste sera itérée dans l'ordre inverse de votre liste.

Juste un indice: vous devriez clairement indiquer ce comportement spécial de votre liste dans la documentation (encore mieux en choisissant un nom de classe explicite, comme Stack ou Queue, également)).

2
Beachwalker

N ° ForEach se contente de parcourir la collection pour chaque élément et chaque commande dépend du fait qu’il utilise IEnumerable ou GetEnumerator ().

2
Josip Medved

Elaborateling légèrement sur la réponse de Nice par Jon Skeet , cela pourrait être polyvalent:

public static IEnumerable<T> Directional<T>(this IList<T> items, bool Forwards) {
    if (Forwards) foreach (T item in items) yield return item;
    else for (int i = items.Count-1; 0<=i; i--) yield return items[i];
}

Et puis utiliser comme

foreach (var item in myList.Directional(forwardsCondition)) {
    .
    .
}
0
Eske Rahn