web-dev-qa-db-fra.com

Foreach loop, détermine quelle est la dernière itération de la boucle

J'ai une boucle foreach et j'ai besoin d'exécuter une certaine logique lorsque le dernier élément est choisi dans la 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? 

194
mishap

Si vous avez juste besoin de faire quelque chose avec le dernier élément (par opposition à quelque chose différent avec le dernier élément, utiliser LINQ vous aidera 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
    }
}

Bien que vous ayez probablement besoin d'écrire un comparateur personnalisé pour vous assurer que vous pouvez savoir que l'élément est identique à celui renvoyé par Last().

Cette approche doit être utilisée avec prudence, car Last devra peut-être parcourir la collection. Bien que cela ne pose peut-être pas un problème pour les petites collections, son agrandissement peut avoir des conséquences sur les performances. Il échouera également si la liste contient des éléments en double. Dans ce cas, quelque chose comme ceci peut être plus approprié:

int totalCount = result.Count();
for (int count = 0; count < totalCount; count++)
{
    Item result = Model.Results[count];
    count++;
    // do something with each item
    if (count == totalCount)
    {
        // do something different with the last item
    }
    else
    {
        // do something different with every item but the last
    }
}
242
ChrisF

Que diriez-vous d'une bonne vieille boucle pour la boucle?

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

Comme Chris le montre, Linq fonctionnera; utilisez simplement Last () pour obtenir une référence à la dernière dans l'énumérable, et tant que vous ne travaillez pas avec cette référence, créez votre code normal, mais si vous travaillez avec cette référence, faites votre travail supplémentaire. Son inconvénient est que ce sera toujours la complexité O (N).

Vous pouvez utiliser à la place Count () (qui est O(1) si IEnumerable est également une ICollection; cela est vrai pour la plupart des IEnumerables intégrés communs) et hybride 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
 }
35
KeithS

L'utilisation de Last() sur certains types va parcourir toute la collection!
Cela signifie que si vous faites une foreach et appelez Last(), vous avez bouclé deux fois! que vous voudriez sûrement éviter dans les grandes collections.

Ensuite, la solution consiste à utiliser une boucle do while:

using (var enumerator = collection.GetEnumerator())
{

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

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

    //process item

    last = !enumerator.MoveNext();        

    //process item extension according to flag; flag means item

  }
}

Test

Sauf si le type de collection est de type IList<T>, la fonction Last() parcourra tous les éléments de la collection.

28
Shimmy
foreach (var item in objList)
{
  if(objList.LastOrDefault().Equals(item))
  {

  }
}
18
Gabriel Tiburcio

Comme Shimmy l’a souligné, l’utilisation de Last () peut être un problème de performances, par exemple si votre collection est le résultat direct d’une expression LINQ. Pour éviter plusieurs itérations, vous pouvez utiliser une méthode d'extension "ForEach" comme celle-ci:

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 ressemble à ceci (en prime, elle vous indiquera également l'indice et si vous regardez 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; }
    }
}
10
Daniel Wolf

L'implémentation d'itérateur ne fournit pas cela. Votre collection peut être une IList accessible via un index dans O (1). Dans ce cas, vous pouvez utiliser une boucle for- normale:

for(int i = 0; i < Model.Results.Count; i++)
{
  if(i == Model.Results.Count - 1) doMagic();
}

Si vous connaissez le nombre mais ne pouvez pas y accéder via des index (le résultat est donc une ICollection), vous pouvez vous compter en incrémentant une i dans le corps de la foreach et en la comparant à sa longueur.

Tout cela n'est pas parfaitement élégant. La solution de Chris est peut-être la plus agréable que j'ai vue jusqu'à présent.

6
Matthias Meid

Qu'en est-il de l'approche peu plus simple.

Item last = null;
foreach (Item result in Model.Results)
{
    // do something with each item

    last = result;
}

//Here Item 'last' contains the last object that came in the last of foreach loop.
DoSomethingOnLastElement(last);
5
faisal

La meilleure approche consisterait probablement simplement à exécuter cette étape après la boucle: par exemple.

foreach(Item result in Model.Results)
{
   //loop logic
}

//Post execution logic

Ou si vous avez besoin de faire quelque chose jusqu'au dernier résultat

foreach(Item result in Model.Results)
{
   //loop logic
}

Item lastItem = Model.Results[Model.Results.Count - 1];

//Execute logic on lastItem here
4
Dustin Hodges

La réponse acceptée ne fonctionnera pas pour les doublons de la collection. Si vous définissez la variable foreach, vous pouvez simplement ajouter vos propres variables d'indexation.

int last = Model.Results.Count - 1;
int index = 0;
foreach (Item result in Model.Results)
{
    //Do Things

    if (index == last)
        //Do Things with the last result

    index++;
}
2
Ehryk

En améliorant Daniel Wolf répond même plus loin vous pouvez empiler sur une autre IEnumerable pour éviter plusieurs itérations et lambdas tels que:

var elements = new[] { "A", "B", "C" };
foreach (var e in elements.Detailed())
{
    if (!e.IsLast) {
        Console.WriteLine(e.Value);
    } else {
        Console.WriteLine("Last one: " + e.Value);
    }
}

L'implémentation de la méthode d'extension:

public static class EnumerableExtensions {
    public static IEnumerable<IterationElement<T>> Detailed<T>(this IEnumerable<T> source)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));

        using (var enumerator = source.GetEnumerator())
        {
            bool isFirst = true;
            bool hasNext = enumerator.MoveNext();
            int index = 0;
            while (hasNext)
            {
                T current = enumerator.Current;
                hasNext = enumerator.MoveNext();
                yield return new IterationElement<T>(index, current, isFirst, !hasNext);
                isFirst = false;
                index++;
            }
        }
    }

    public struct IterationElement<T>
    {
        public int Index { get; }
        public bool IsFirst { get; }
        public bool IsLast { get; }
        public T Value { get; }

        public IterationElement(int index, T value, bool isFirst, bool isLast)
        {
            Index = index;
            IsFirst = isFirst;
            IsLast = isLast;
            Value = value;
        }
    }
}
2
Fabricio Godoy

".Last ()" n'a pas fonctionné pour moi, j'ai donc dû faire quelque chose comme ceci:

Dictionary<string, string> iterativeDictionary = someOtherDictionary;
var index = 0;
iterativeDictionary.ForEach(kvp => 
    index++ == iterativeDictionary.Count ? 
        /*it's the last item */ :
        /*it's not the last item */
);
1
itcropper

En apportant quelques modifications au code excellent de Jon Skeet, vous pouvez même le rendre plus intelligent en permettant l’accès à l’article précédent et suivant. Bien sûr, cela signifie que vous devrez lire un élément à l’avance dans la mise en œuvre. Pour des raisons de performances, les éléments précédent et suivant ne sont conservés que pour l'élément d'itération en cours. Ça va comme ça:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
// Based on source: http://jonskeet.uk/csharp/miscutil/

namespace Generic.Utilities
{
    /// <summary>
    /// Static class to make creation easier. If possible though, use the extension
    /// method in SmartEnumerableExt.
    /// </summary>
    public static class SmartEnumerable
    {
        /// <summary>
        /// Extension method to make life easier.
        /// </summary>
        /// <typeparam name="T">Type of enumerable</typeparam>
        /// <param name="source">Source enumerable</param>
        /// <returns>A new SmartEnumerable of the appropriate type</returns>
        public static SmartEnumerable<T> Create<T>(IEnumerable<T> source)
        {
            return new SmartEnumerable<T>(source);
        }
    }

    /// <summary>
    /// Type chaining an IEnumerable&lt;T&gt; to allow the iterating code
    /// to detect the first and last entries simply.
    /// </summary>
    /// <typeparam name="T">Type to iterate over</typeparam>
    public class SmartEnumerable<T> : IEnumerable<SmartEnumerable<T>.Entry>
    {

        /// <summary>
        /// Enumerable we proxy to
        /// </summary>
        readonly IEnumerable<T> enumerable;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="enumerable">Collection to enumerate. Must not be null.</param>
        public SmartEnumerable(IEnumerable<T> enumerable)
        {
            if (enumerable == null)
            {
                throw new ArgumentNullException("enumerable");
            }
            this.enumerable = enumerable;
        }

        /// <summary>
        /// Returns an enumeration of Entry objects, each of which knows
        /// whether it is the first/last of the enumeration, as well as the
        /// current value and next/previous values.
        /// </summary>
        public IEnumerator<Entry> GetEnumerator()
        {
            using (IEnumerator<T> enumerator = enumerable.GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    yield break;
                }
                bool isFirst = true;
                bool isLast = false;
                int index = 0;
                Entry previous = null;

                T current = enumerator.Current;
                isLast = !enumerator.MoveNext();
                var entry = new Entry(isFirst, isLast, current, index++, previous);                
                isFirst = false;
                previous = entry;

                while (!isLast)
                {
                    T next = enumerator.Current;
                    isLast = !enumerator.MoveNext();
                    var entry2 = new Entry(isFirst, isLast, next, index++, entry);
                    entry.SetNext(entry2);
                    yield return entry;

                    previous.UnsetLinks();
                    previous = entry;
                    entry = entry2;                    
                }

                yield return entry;
                previous.UnsetLinks();
            }
        }

        /// <summary>
        /// Non-generic form of GetEnumerator.
        /// </summary>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Represents each entry returned within a collection,
        /// containing the value and whether it is the first and/or
        /// the last entry in the collection's. enumeration
        /// </summary>
        public class Entry
        {
            #region Fields
            private readonly bool isFirst;
            private readonly bool isLast;
            private readonly T value;
            private readonly int index;
            private Entry previous;
            private Entry next = null;
            #endregion

            #region Properties
            /// <summary>
            /// The value of the entry.
            /// </summary>
            public T Value { get { return value; } }

            /// <summary>
            /// Whether or not this entry is first in the collection's enumeration.
            /// </summary>
            public bool IsFirst { get { return isFirst; } }

            /// <summary>
            /// Whether or not this entry is last in the collection's enumeration.
            /// </summary>
            public bool IsLast { get { return isLast; } }

            /// <summary>
            /// The 0-based index of this entry (i.e. how many entries have been returned before this one)
            /// </summary>
            public int Index { get { return index; } }

            /// <summary>
            /// Returns the previous entry.
            /// Only available for the CURRENT entry!
            /// </summary>
            public Entry Previous { get { return previous; } }

            /// <summary>
            /// Returns the next entry for the current iterator.
            /// Only available for the CURRENT entry!
            /// </summary>
            public Entry Next { get { return next; } }
            #endregion

            #region Constructors
            internal Entry(bool isFirst, bool isLast, T value, int index, Entry previous)
            {
                this.isFirst = isFirst;
                this.isLast = isLast;
                this.value = value;
                this.index = index;
                this.previous = previous;
            }
            #endregion

            #region Methods
            /// <summary>
            /// Fix the link to the next item of the IEnumerable
            /// </summary>
            /// <param name="entry"></param>
            internal void SetNext(Entry entry)
            {
                next = entry;
            }

            /// <summary>
            /// Allow previous and next Entry to be garbage collected by setting them to null
            /// </summary>
            internal void UnsetLinks()
            {
                previous = null;
                next = null;
            }

            /// <summary>
            /// Returns "(index)value"
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return String.Format("({0}){1}", Index, Value);
            }
            #endregion

        }
    }
}
1
Edwin

Vous pouvez créer une méthode d’extension spécialement dédiée à cela:

public static class EnumerableExtensions {
    public static bool IsLast<T>(this List<T> items, T item)
        {
            if (items.Count == 0)
                return false;
            T last = items[items.Count - 1];
            return item.Equals(last);
        }
    }

et vous pouvez l'utiliser comme ceci:

foreach (Item result in Model.Results)
{
    if(Model.Results.IsLast(result))
    {
        //do something in the code
    }
}
0
A. Morel

Pour ajouter quelque chose à chaque élément, à l'exception du dernier, une approche basée sur les fonctions peut être utilisée.

delegate void DInner ();

....
    Dinner inner=delegate 
    { 
        inner=delegate 
        { 
            // do something additional
        } 
    }
    foreach (DataGridViewRow dgr in product_list.Rows)
    {
        inner()
        //do something
    }
}

Cette approche présente des inconvénients apparents: moins de clarté du code pour les cas plus complexes. Appeler des délégués peut ne pas être très efficace. Dépannage pourrait ne pas être assez facile. Le bon côté des choses - le codage est amusant!

Cela dit, je suggérerais d'utiliser des boucles simples pour les cas simples, si vous savez que le nombre de vos collections n'est pas très lent.

0
dmitry

Jon Skeet a créé un type SmartEnumerable<T> il y a quelque temps pour résoudre ce problème. Vous pouvez voir sa mise en œuvre ici:

http://codeblog.jonskeet.uk/2007/07/27/smart-enumerations/

Pour télécharger: http://www.yoda.arachsys.com/csharp/miscutil/

0
Spencer Ruport
     List<int> ListInt = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };


                int count = ListInt.Count;
                int index = 1;
                foreach (var item in ListInt)
                {
                    if (index != count)
                    {
                        Console.WriteLine("do something at index number  " + index);
                    }
                    else
                    {
                        Console.WriteLine("Foreach loop, this is the last iteration of the loop " + index);
                    }
                    index++;

                }
 //OR
                int count = ListInt.Count;
                int index = 1;
                foreach (var item in ListInt)
                {
                    if (index < count)
                    {
                        Console.WriteLine("do something at index number  " + index);
                    }
                    else
                    {
                        Console.WriteLine("Foreach loop, this is the last iteration of the loop " + index);
                    }
                    index++;

                }
0
Zoyeb Shaikh

Une autre façon, que je n'ai pas vue publiée, est d'utiliser une file d'attente. Cela ressemble à un moyen d'implémenter une méthode SkipLast () sans itérer plus que nécessaire. De cette façon, vous pourrez également le faire pour n’importe quel nombre de derniers éléments.

public static void ForEachAndKnowIfLast<T>(
    this IEnumerable<T> source,
    Action<T, bool> a,
    int numLastItems = 1)
{
    int bufferMax = numLastItems + 1;
    var buffer = new Queue<T>(bufferMax);
    foreach (T x in source)
    {
        buffer.Enqueue(x);
        if (buffer.Count < bufferMax)
            continue; //Until the buffer is full, just add to it.
        a(buffer.Dequeue(), false);
    }
    foreach (T item in buffer)
        a(item, true);
}

Pour appeler cela, procédez comme suit:

Model.Results.ForEachAndKnowIfLast(
    (result, isLast) =>
    {
        //your logic goes here, using isLast to do things differently for last item(s).
    });
0
rrreee

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

https://code.i-harness.com/fr/q/7213ce

0
HanMyintTun

Stockez simplement la valeur précédente et utilisez-la dans la boucle. Ensuite, à la fin, la valeur "précédente" sera le dernier élément, vous permettant de le gérer différemment. Pas de comptage ou de bibliothèques spéciales requises.

bool empty = true;
Item previousItem;

foreach (Item result in Model.Results)
{
    if (!empty)
    {
        // We know this isn't the last item because it came from the previous iteration
        handleRegularItem(previousItem);
    }

    previousItem = result;
    empty = false;
}

if (!empty)
{
    // We know this is the last item because the loop is finished
    handleLastItem(previousItem);
}
0
voltrevo

Comment convertir foreach pour réagir au dernier élément:

List<int> myList = new List<int>() {1, 2, 3, 4, 5};
Console.WriteLine("foreach version");
{
    foreach (var current in myList)
    {
        Console.WriteLine(current);
    }
}
Console.WriteLine("equivalent that reacts to last element");
{
    var enumerator = myList.GetEnumerator();
    if (enumerator.MoveNext() == true) // Corner case: empty list.
    {
        while (true)
        {
            int current = enumerator.Current;

            // Handle current element here.
            Console.WriteLine(current);

            bool ifLastElement = (enumerator.MoveNext() == false);
            if (ifLastElement)
            {
                // Cleanup after last element
                Console.WriteLine("[last element]");
                break;
            }
        }
    }
    enumerator.Dispose();
}
0
Contango

Vous pouvez simplement utiliser une boucle for et il n'est pas nécessaire d'ajouter une if supplémentaire dans le corps de for

for (int i = 0; i < Model.Results.Count - 1; i++) {
    var item = Model.Results[i];
}

Le -1 dans la condition for permet de sauter le dernier élément.

0
Alisson