267 votes

Boucle Foreach, déterminer quelle est la dernière itération de la boucle

J'ai un foreach et j'ai besoin d'exécuter une certaine logique lorsque le dernier élément est choisi dans la liste. List par exemple :

 foreach (Item result in Model.Results)
 {
      //if current result is the last item in Model.Results
      //then do something in the code
 }

Puis-je savoir quelle boucle est la dernière sans utiliser la boucle for et les compteurs ?

1 votes

Jetez un coup d'œil à ma réponse aquí pour une solution que j'ai postée à une question connexe.

0 votes

331voto

ChrisF Points 74295

Si vous avez juste besoin de faire quelque chose avec le dernier élément (par opposition à quelque chose de différents avec le dernier élément, alors l'utilisation de LINQ sera utile ici :

Item last = Model.Results.Last();
// do something with last

Si vous avez besoin de faire quelque chose de différent avec le dernier élément, vous aurez besoin de quelque chose comme :

Item last = Model.Results.Last();
foreach (Item result in Model.Results)
{
    // do something with each item
    if (result.Equals(last))
    {
        // do something different with the last item
    }
    else
    {
        // do something different with every item but the last
    }
}

Cependant, vous devrez probablement écrire un comparateur personnalisé pour vous assurer que l'élément est le même que celui renvoyé par la méthode Last() .

Cette approche doit être utilisée avec prudence car Last peut avoir à itérer dans la collection. Si ce n'est pas un problème pour les petites collections, cela peut avoir des conséquences sur les performances si la collection devient importante. Cette méthode échouera également si la liste contient des éléments en double. Dans ce cas, quelque chose comme ceci pourrait être plus approprié :

int totalCount = result.Count();
for (int count = 0; count < totalCount; count++)
{
    Item result = Model.Results[count];

    // do something with each item
    if ((count + 1) == totalCount)
    {
        // do something different with the last item
    }
    else
    {
        // do something different with every item but the last
    }
}

1 votes

Ce dont j'avais besoin était : Quand la boucle passe par son dernier élément : foreach (Item result in Model.Results) { if (result == Model.Results.Last()) { <div>last</div> ; } Il semble que vous vouliez à peu près la même chose.

10 votes

Votre code va itérer deux fois à travers la collection entière - mauvais si la collection n'est pas petite. Voir ce réponse.

61 votes

Cela ne fonctionne pas vraiment si vous avez des doublons dans votre collection. Par exemple, si vous travaillez avec une collection de chaînes de caractères et qu'il y a des doublons, le code "différent avec le dernier élément" sera exécuté pour chaque occurrence du dernier élément de la liste.

205voto

Que diriez-vous d'un bon vieux "for loop" ?

for (int i = 0; i < Model.Results.Count; i++) {

     if (i == Model.Results.Count - 1) {
           // this is the last item
     }
}

Ou en utilisant Linq et le foreach :

foreach (Item result in Model.Results)   
{   
     if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
             // this is the last item
     }
}

17 votes

Il y a tellement de gens qui réfléchissent trop à un problème aussi simple que celui-ci, alors que la boucle for est parfaitement capable de le faire déjà. :\\N- Je ne sais pas.

0 votes

La solution Linq est ma préférée ! Merci de partager

0 votes

Cette réponse est plus appropriée que la réponse acceptée.

49voto

KeithS Points 36130

Comme Chris le montre, Linq fonctionnera ; il suffit d'utiliser Last() pour obtenir une référence à la dernière personne de l'énumérable, et tant que vous ne travaillez pas avec cette référence, faites votre code normal, mais si vous travaillez avec cette référence, faites votre truc supplémentaire. Son inconvénient est qu'il sera toujours d'une complexité O(N).

Vous pouvez à la place utiliser Count() (qui est O(1) si le IEnumerable est aussi un ICollection ; c'est vrai pour la plupart des IEnumerables intégrés courants), et hybridez votre foreach avec un compteur :

var i=0;
var count = Model.Results.Count();
foreach (Item result in Model.Results)
{
    if (++i == count) //this is the last item
}

47voto

Shimmy Points 23393

Utilisation de Last() sur certains types d'appareils permet de parcourir toute la collection en boucle !
Ce qui signifie que si vous faites un foreach et appeler Last() vous avez fait une boucle deux fois ! ce que je suis sûr que vous aimeriez éviter dans les grandes collections.

La solution consiste alors à utiliser un do while boucle :

using var enumerator = collection.GetEnumerator();

var last = !enumerator.MoveNext();
T current;

while (!last)
{
  current = enumerator.Current;        

  //process item

  last = !enumerator.MoveNext();        
  if(last)
  {
    //additional processing for last item
  }
}

Donc, à moins que le type de la collection soit de type IList<T> el Last() va itérer à travers tous les éléments de la collection.

Test

Si votre collection fournit un accès aléatoire (par exemple, implémente IList<T> ), vous pouvez également vérifier votre article comme suit.

if(collection is IList<T> list)
  return collection[^1]; //replace with collection.Count -1 in pre-C#8 apps

1 votes

Êtes-vous sûr que l'énumérateur a besoin d'une using déclaration ? Je pensais que cela n'était nécessaire que si un objet manipule des ressources du système d'exploitation, mais pas pour les structures de données gérées.

1 votes

IEnumerator ne met pas en œuvre IDisposable, donc la ligne avec l'utilisation de avec soulève une erreur de compilation ! +1 pour la solution, la plupart du temps nous ne pouvons pas simplement utiliser un for au lieu d'un foreach, parce que les éléments des collections énumérables sont calculés au moment de l'exécution ou la séquence ne supporte pas l'accès aléatoire.

1 votes

El générique fait.

12voto

Daniel Wolf Points 1528

Comme Shimmy l'a souligné, l'utilisation de Last() peut poser un problème de performance, par exemple si votre collection est le résultat direct d'une expression LINQ. Pour éviter les itérations multiples, vous pourriez utiliser une méthode d'extension "ForEach" comme ceci :

var elements = new[] { "A", "B", "C" };
elements.ForEach((element, info) => {
    if (!info.IsLast) {
        Console.WriteLine(element);
    } else {
        Console.WriteLine("Last one: " + element);
    }
});

La méthode d'extension se présente comme suit (en prime, elle vous indique également l'indice et si vous êtes en train de regarder le premier élément) :

public static class EnumerableExtensions {
    public delegate void ElementAction<in T>(T element, ElementInfo info);

    public static void ForEach<T>(this IEnumerable<T> elements, ElementAction<T> action) {
        using (IEnumerator<T> enumerator = elements.GetEnumerator())
        {
            bool isFirst = true;
            bool hasNext = enumerator.MoveNext();
            int index = 0;
            while (hasNext)
            {
                T current = enumerator.Current;
                hasNext = enumerator.MoveNext();
                action(current, new ElementInfo(index, isFirst, !hasNext));
                isFirst = false;
                index++;
            }
        }
    }

    public struct ElementInfo {
        public ElementInfo(int index, bool isFirst, bool isLast)
            : this() {
            Index = index;
            IsFirst = isFirst;
            IsLast = isLast;
        }

        public int Index { get; private set; }
        public bool IsFirst { get; private set; }
        public bool IsLast { get; private set; }
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X