Цикл Foreach, определите, какая последняя итерация цикла - PullRequest
202 голосов
/ 19 сентября 2011

У меня есть цикл foreach, и мне нужно выполнить некоторую логику, когда последний элемент выбран из List, например ::

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

Могу ли я узнать, какой цикл является последним без использования цикла и счетчиков?

Ответы [ 25 ]

252 голосов
/ 19 сентября 2011

Если вам просто нужно что-то сделать с последним элементом (в отличие от чего-то отличается с последним элементом, то здесь поможет LINQ:

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

Если вам нужносделайте что-то другое с последним элементом, тогда вам понадобится что-то вроде:

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

Хотя вам, вероятно, потребуется написать собственный компаратор, чтобы убедиться, что вы можете сказать, что элемент совпадает с элементомвозвращается Last().

Этот подход следует использовать с осторожностью, так как Last вполне может потребоваться перебрать коллекцию. Хотя это может не быть проблемой для небольших коллекций, если она становится большой, она может иметьпоследствия для производительности. Он также потерпит неудачу, если список содержит повторяющиеся элементы. В этом случае может быть более уместным что-то подобное:

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
    }
}
157 голосов
/ 19 сентября 2011

Как насчет старой доброй петли?

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

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

Или с использованием Linq и foreach:

foreach (Item result in Model.Results)   
{   
     if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
             // this is the last item
     }
}
36 голосов
/ 19 сентября 2011

Как показывает Крис, Линк будет работать;просто используйте Last (), чтобы получить ссылку на последнюю в перечислимом, и, если вы не работаете с этой ссылкой, делайте обычный код, но если вы работаете с этой ссылкой, делайте свою дополнительную вещь.Его недостатком является то, что он всегда будет O (N) -сложностью.

Вместо этого вы можете использовать Count () (который равен O (1), если IEnumerable также является ICollection; это верно для большей частиобычные встроенные IEnumerables) и объедините ваш foreach со счетчиком:

var i=0;
var count = Model.Results.Count();
foreach (Item result in Model.Results)
 {
      if(++i==count) //this is the last item
 }
34 голосов
/ 04 июля 2013

Использование Last() на определенных типах зацикливает всю коллекцию!
Это означает, что если вы делаете foreach и вызываете Last(), вы зациклили дважды! , что, я уверен,Вы хотели бы избежать в больших коллекциях.

Тогда решение состоит в том, чтобы использовать цикл 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

  }
}

Тест

Если тип коллекции не относится к типу IList<T>, функция Last() будет выполнять итерацию по всем элементам коллекции.

18 голосов
/ 21 апреля 2016
foreach (var item in objList)
{
  if(objList.LastOrDefault().Equals(item))
  {

  }
}
10 голосов
/ 11 ноября 2013

Как указал Шимми, использование Last () может быть проблемой производительности, например, если ваша коллекция является живым результатом выражения LINQ. Чтобы предотвратить многократные итерации, вы можете использовать метод расширения «ForEach», подобный этому:

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

Метод расширения выглядит следующим образом (в качестве дополнительного бонуса он также сообщит вам индекс и, если вы смотрите на первый элемент):

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; }
    }
}
6 голосов
/ 19 сентября 2011

Реализация итератора не обеспечивает этого.Ваша коллекция может быть IList, которая доступна через индекс в O (1).В этом случае вы можете использовать обычный for -loop:

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

Если вы знаете количество, но не можете получить доступ через индексы (таким образом, результат равен ICollection), вы можете считать себя какувеличение i в теле foreach и сравнение его с длиной.

Все это не совсем элегантно.Решение Криса может быть лучшим из тех, что я видел до сих пор.

5 голосов
/ 26 июня 2014

А как насчет немного более простого подхода?

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);
4 голосов
/ 27 июля 2017

Улучшение Ответ Даниэля Вольфа еще больше вы можете сложить на другой IEnumerable, чтобы избежать нескольких итераций и лямбд, таких как:

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

Реализация метода расширения:

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;
        }
    }
}
4 голосов
/ 19 сентября 2011

Лучшим подходом, вероятно, будет просто выполнить этот шаг после цикла: например,

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

//Post execution logic

Или если вам нужно что-то сделать с последним результатом

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

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

//Execute logic on lastItem here
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...