Foreach循环,确定哪个是循环的最后一次迭代


233

我有一个foreach循环,当从中选择了最后一项时,需要执行一些逻辑List,例如:

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

我可以不使用for循环和计数器就知道哪个循环是最后一个吗?


1
这里查看我的答案以了解我发布到相关问题的解决方案。
Brian Gideon

Answers:


294

如果你只是需要做一些事情的最后一个元素(而不是一些不同与当时的最后一个元素使用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];

    // 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
我需要的是:当循环通过其最后一个项目时:foreach(项目结果在Model.Results中){if(result == Model.Results.Last()){<div> last </ div>; }您似乎在说同样的话。
事故发生

10
您的代码将遍历整个集合两次,如果集合不小则不好。看到这个答案。
Shimmy Weitzhandler

54
如果您的收藏夹中有重复项,这实际上是行不通的。例如,如果您使用的是字符串集合,并且有任何重复项,则对于列表中最后一个项的每次出现,将执行“与最后一个项不同”代码。
muttley91 2013年

7
该答案很旧,但是对于其他查看此答案的人,您可以使用以下方法获取最后一个元素并确保不必遍历这些元素:Item last = Model.Results [Model.Results.Count-1]列表的属性不需要循环。如果列表中有重复项,则只需在for循环中使用迭代器变量。常规的for循环还不错。
迈克尔·哈里斯

我建议使用var last = Model.Result[Model.Result.Count - 1];比使用更快Last()

184

一个好的老式for循环怎么样?

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

14
当for循环已经完全能够做到这一点时,许多ppl都在思考这样的简单问题。:\
安德鲁·霍夫曼

Linq解决方案是我绝对的最爱!感谢您的分享
mecograph '19

这是比公认的答案更合适的答案。
拉图尔

请注意要在一组字符串(或值类型)上使用LINQ解决方案的人:它通常不起作用,因为如果列表中的最后一个字符串也出现在列表的前面,则==比较将失败。仅当您使用保证没有重复字符串的列表时,它才有效。
Tawab Wakil

不幸的是,如果Model.Results是,您将无法使用此聪明的解决方案IEnumerable。您可以Count()在循环之前调用,但这可能会导致序列的完整迭代。
Luca Cremonesi

42

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();        
  if(last)
  {
    //additional processing for last item
  }
}

因此,除非集合类型是类型IList<T>,否则该Last()函数将遍历所有集合元素。

测试

如果您的收藏集提供了随机访问权限(例如工具IList<T>),则您还可以按照以下方式检查商品。

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

1
您确定枚举器需要using声明吗?我认为只有在对象处理操作系统资源时才需要,而对于托管数据结构则不需要。
卧虎藏龙小猫

IEnumerator没有实现IDisposable,因此与with一起使用会引起编译时错误!对于解决方案+1,在大多数情况下,我们不能只是简单地使用for而不是foreach,因为可枚举的集合项是在运行时计算的,或者该序列不支持随机访问。
萨利赫


40

正如Chris所示,Linq会工作;只需使用Last()来获取可枚举中最后一个的引用,并且只要您不使用该引用,就可以执行常规代码,但是如果您要使用该引用,则可以执行其他操作。它的缺点是它将始终是O(N)-复杂性。

您可以改用Count()(如果IEnumerable也是ICollection,则为O(1);对于大多数常见的内置IEnumerables都是如此),并将您的foreach与计数器混合:

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

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

  }
}

您好,这是迄今为止最好的方法!简单明了。一种程序员思维方法,一种。我们为什么不选择并给这个+1更多!
汉妮·赛迪亚万

1
在块之前,只能找到一次最后一个项目(Promote memoizationforeach。像这样:var lastItem = objList.LastOrDeafault();。然后从内部foreach循环,你可以检查一下这种方式:f (item.Equals(lastItem)) { ... }。在您的原始答案中,objList.LastOrDefault()它将在每次“ foreach”迭代中对集合进行迭代(涉及复杂度)。
AlexMelw

错误的答案。n ^ 2复杂度而不是n。
Shimmy Weitzhandler

11

正如Shimmy所指出的,例如,如果您的集合是LINQ表达式的实时结果,则使用Last()可能会带来性能问题。为了防止多次迭代,您可以使用“ 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; }
    }
}

9

进一步提高Daniel Wolf的答案,您可以将其堆叠在一起,IEnumerable以避免多次迭代和lambda,例如:

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

1
另一个答案不会多次迭代源,因此这不是您要解决的问题。您确实允许使用foreach,这是一个改进。
Servy '17

1
@Servy我的意思是。除了原始答案的一次迭代外,我还避免使用lambda。
Fabricio Godoy

7

迭代器实现不提供该功能。您的集合可能IList是可通过O(1)中的索引访问的集合。在这种情况下,您可以使用普通for -loop:

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

如果您知道计数,但无法通过索引访问(因此结果为ICollection),则可以通过递增一个来计数自己iforeach的正文中并将其与长度进行比较来对。

所有这些都不完美。克里斯的解决方案可能是到目前为止我所见过的最好的解决方案。


在foreach想法和Chris解决方案中比较计数器的性能时,我想知道哪个会花费更多—单个Last()调用或所有添加的增量操作的总和。我怀疑那会很近。
TTT 2014年

6

那么简单的方法呢?

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

2
我不知道为什么有人不赞成你。考虑到您已经在执行foreach并且产生了o(n)的成本,这是完全可以接受的。
arviman

2
尽管答案很适合找出最后一个项目,但它不适合OP的条件...,确定哪个是循环的最后一次迭代 ”。因此,您无法确定最后一次迭代实际上是最后一次迭代,因此,您无法以不同的方式处理它,甚至无法忽略它。这就是有人拒绝您的原因。@arviman,您对此很好奇。
AlexMelw

1
没错,我完全错过了@ Andrey-WD。我猜想解决的方法是在循环之前调用“ last”一次(不能在循环内执行,因为它会是O(N ^ 2),然后检查引用是否匹配它。)
arviman

5

最好的方法可能只是在循环后执行该步骤:例如

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

3

接受的答案不适用于集合中的重复项。如果您将设置为foreach,则只需添加自己的索引变量即可。

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


1

“ .Last()”对我不起作用,所以我必须做这样的事情:

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

对Jon Skeet的优秀代码进行一些小的调整,甚至可以通过允许访问上一个和下一个项目来使其更智能。当然,这意味着您必须提前阅读实施中的一项。出于性能原因,仅保留当前迭代项的上一项和下一项。它是这样的:

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

如何转换foreach以对最后一个元素作出反应:

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

1

只需存储先前的值并在循环内使用它即可。然后最后,“上一个”值将是最后一个项目,让您以不同的方式处理它。无需计数或特殊库。

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

1

您可以只使用for循环,而无需iffor主体内添加额外的内容:

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

-1for条件需要跳过最后的产品负责。


for循环中的-1不会跳过最后一项。如果不包含-1,则将获得IndexOutOfRangeException。
Jaa H


0

为了对除最后一个元素之外的每个元素执行其他操作,可以使用基于函数的方法。

delegate void DInner ();

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

这种方法有明显的缺点:对于更复杂的情况,代码清晰度较低。呼叫代表可能不是很有效。故障排除可能不太容易。好的一面-编码很有趣!

话虽如此,我建议在琐碎的情况下使用普通的for循环,如果您知道集合的计数不是很慢的话。


0

我没有看到的另一种方式是使用队列。它类似于无需重复进行更多操作即可实现SkipLast()方法的方法。这种方式还可以让您对任意数量的最后一件商品执行此操作。

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

要调用此方法,您需要执行以下操作:

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

0
     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

您可以专门为此设置扩展方法:

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

您可以像这样使用它:

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

0

基于@Shimmy的响应,我创建了一个扩展方法,这是每个人都想要的解决方案。它简单,易于使用,并且仅循环浏览一次集合。

internal static class EnumerableExtensions
{
    public static void ForEachLast<T>(this IEnumerable<T> collection, Action<T>? actionExceptLast = null, Action<T>? actionOnLast = null)
    {
        using var enumerator = collection.GetEnumerator();
        var isNotLast = enumerator.MoveNext();
        while (isNotLast)
        {
            var current = enumerator.Current;
            isNotLast = enumerator.MoveNext();
            var action = isNotLast ? actionExceptLast : actionOnLast;
            action?.Invoke(current);
        }
    }
}

这适用于任何IEnumerable<T>。用法如下所示:

var items = new[] {1, 2, 3, 4, 5};
items.ForEachLast(i => Console.WriteLine($"{i},"), i => Console.WriteLine(i));

输出如下:

1,
2,
3,
4,
5

此外,您可以将其设置为Select样式方法。然后,在中重用该扩展名ForEach。该代码如下所示:

internal static class EnumerableExtensions
{
    public static void ForEachLast<T>(this IEnumerable<T> collection, Action<T>? actionExceptLast = null, Action<T>? actionOnLast = null) =>
        // ReSharper disable once IteratorMethodResultIsIgnored
        collection.SelectLast(i => { actionExceptLast?.Invoke(i); return true; }, i => { actionOnLast?.Invoke(i); return true; }).ToArray();

    public static IEnumerable<TResult> SelectLast<T, TResult>(this IEnumerable<T> collection, Func<T, TResult>? selectorExceptLast = null, Func<T, TResult>? selectorOnLast = null)
    {
        using var enumerator = collection.GetEnumerator();
        var isNotLast = enumerator.MoveNext();
        while (isNotLast)
        {
            var current = enumerator.Current;
            isNotLast = enumerator.MoveNext();
            var selector = isNotLast ? selectorExceptLast : selectorOnLast;
            //https://stackoverflow.com/a/32580613/294804
            if (selector != null)
            {
                yield return selector.Invoke(current);
            }
        }
    }
}

-1

我们可以检查循环中的最后一项。

foreach (Item result in Model.Results)
{
    if (result==Model.Results.Last())
    {
        // do something different with the last item
    }
}

-2
foreach (DataRow drow in ds.Tables[0].Rows)
            {
                cnt_sl1 = "<div class='col-md-6'><div class='Slider-img'>" +
                          "<div class='row'><img src='" + drow["images_path"].ToString() + "' alt='' />" +
                          "</div></div></div>";
                cnt_sl2 = "<div class='col-md-6'><div class='Slider-details'>" +
                          "<p>" + drow["situation_details"].ToString() + "</p>" +
                          "</div></div>";
                if (i == 0)
                {
                    lblSituationName.Text = drow["situation"].ToString();
                }
                if (drow["images_position"].ToString() == "0")
                {
                    content += "<div class='item'>" + cnt_sl1 + cnt_sl2 + "</div>";
                    cnt_sl1 = "";
                    cnt_sl2 = "";
                }
                else if (drow["images_position"].ToString() == "1")
                {
                    content += "<div class='item'>" + cnt_sl2 + cnt_sl1 + "</div>";
                    cnt_sl1 = "";
                    cnt_sl2 = "";
                }
                i++;
            }

(!)无论您的代码有多好。没有解释,它通常没有价值。
AlexMelw

也似乎工程过度。
mecograph

-3

您可以这样做:

foreach (DataGridViewRow dgr in product_list.Rows)
{
    if (dgr.Index == dgr.DataGridView.RowCount - 1)
    {
        //do something
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.