关于c#:Foreach循环,确定哪个是循环的最后一次迭代

Foreach loop, determine which is the last iteration of the loop

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

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

在不使用for循环和计数器的情况下,我能知道哪个循环是最后一个循环吗?


如果您只需要对最后一个元素做一些事情(而不是对最后一个元素做一些不同的事情),那么使用linq将有助于:

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

如果您需要对最后一个元素做一些不同的事情,那么您需要这样的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
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可能需要遍历集合。虽然这对于小集合来说可能不是问题,但如果它变大了,可能会影响性能。如果列表包含重复项,它也将失败。在这种情况下,这样的事情可能更合适:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
    }
}


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

1
2
3
4
5
6
for (int i = 0; i < Model.Results.Count; i++) {

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

或者使用linq和foreach:

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


正如克里斯所示,LINQ将工作;只需使用LAST()来获取枚举中最后一个的引用,只要您不使用该引用,那么就执行正常的代码,但是如果您正在使用该引用,那么请执行额外的操作。它的缺点是它永远是O(n)-复杂性。

您可以使用count()(如果IEnumerable也是ICollection,则为o(1);对于大多数常见的内置IEnumerable,这是正确的),并将foreach与计数器混合使用:

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

在某些类型上使用Last()将循环整个集合!也就是说,如果你做了一个foreach,然后打电话给Last(),你就循环了两次!我相信在大收藏中你会避免的。

然后,解决方案是使用do while循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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类型,否则Last()函数将遍历所有集合元素。


1
2
3
4
5
6
7
foreach (var item in objList)
{
  if(objList.LastOrDefault().Equals(item))
  {

  }
}


如Shimmy所指出的,使用last()可能是一个性能问题,例如,如果集合是Linq表达式的实时结果。为了防止多次迭代,可以使用如下"foreach"扩展方法:

1
2
3
4
5
6
7
8
var elements = new[] {"A","B","C" };
elements.ForEach((element, info) => {
    if (!info.IsLast) {
        Console.WriteLine(element);
    } else {
        Console.WriteLine("Last one:" + element);
    }
});

扩展方法如下所示(作为额外的奖励,它还将告诉您索引,如果您正在查看第一个元素):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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; }
    }
}

迭代器实现没有提供。您的集合可能是可以通过o(1)中的索引访问的IList。在这种情况下,您可以使用普通的for循环:

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

如果您知道计数,但不能通过索引访问(因此,结果是一个ICollection,您可以通过在foreach的主体中增加一个i,并将其与长度进行比较来计算自己。

这一切都不太优雅。克里斯的解决方案可能是迄今为止我见过的最好的。


简单一点的方法怎么样?

1
2
3
4
5
6
7
8
9
10
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);


最好的方法可能是在循环之后执行这个步骤:

1
2
3
4
5
6
foreach(Item result in Model.Results)
{
   //loop logic
}

//Post execution logic

或者如果你需要为最后的结果做些什么

1
2
3
4
5
6
7
8
foreach(Item result in Model.Results)
{
   //loop logic
}

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

//Execute logic on lastItem here


进一步改进丹尼尔·沃尔夫的答案,您可以堆叠在另一个EDCOX1×1上,以避免多次迭代和lambdas,例如:

1
2
3
4
5
6
7
8
9
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);
    }
}

扩展方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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;
        }
    }
}


接受的答案对集合中的重复项无效。如果在foreach上设置了索引变量,则只需添加自己的索引变量即可。

1
2
3
4
5
6
7
8
9
10
11
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++;
}

".last()"不适合我,所以我必须这样做:

1
2
3
4
5
6
7
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 */
);

对jon skeet的优秀代码做一些小的调整,您甚至可以通过允许访问前一项和下一项使其更智能。当然,这意味着您必须提前阅读实现中的1项。出于性能原因,前一项和下一项只保留当前迭代项。就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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

        }
    }
}

如何转换EDCOX1与0个元素对最后一个元素的反应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}

使用Linq和Foreach:

1
2
3
4
5
6
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/en/q/7213ce


乔恩·斯基特(JonSkeet)不久前创建了一个SmartEnumerableA型,以解决这个确切的问题。您可以在这里看到它的实现:

Smart enumerations

下载:http://www.yoda.arachsys.com/csharp/miscutil/


另一种方法,我没有看到张贴,是使用一个队列。它类似于一种实现skiplast()方法的方法,而不需要进行多次迭代。通过这种方式,您还可以对任何数量的最后一项执行此操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

要调用此函数,请执行以下操作:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
     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++;

                }


您可以制作一个专门用于此的扩展方法:

1
2
3
4
5
6
7
8
9
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);
        }
    }

你可以这样使用它:

1
2
3
4
5
6
7
foreach (Item result in Model.Results)
{
    if(Model.Results.IsLast(result))
    {
        //do something in the code
    }
}

您可以只使用for循环,不需要在for主体中添加额外的if

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

for条件下,-1负责跳过最后一项。


除了最后一个元素之外,要对每个元素做一些额外的事情,可以使用基于函数的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
delegate void DInner ();

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

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

尽管如此,如果您知道您的集合的计数不是非常慢的话,我建议在一些小的情况下使用plain for循环。


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

1
2
3
4
5
6
7
foreach (Item result in Model.Results)
{
    if (result==Model.Results.Last())
    {
        // do something different with the last item
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
foreach (DataRow drow in ds.Tables[0].Rows)
            {
                cnt_sl1 ="" +
                         "<img src='" + drow["images_path"].ToString() +"' alt='' />" +
                         "";
                cnt_sl2 ="" +
                         "<p>
"
+ drow["situation_details"].ToString() +"
</p>"
+
                         "";
                if (i == 0)
                {
                    lblSituationName.Text = drow["situation"].ToString();
                }
                if (drow["images_position"].ToString() =="0")
                {
                    content +="" + cnt_sl1 + cnt_sl2 +"";
                    cnt_sl1 ="";
                    cnt_sl2 ="";
                }
                else if (drow["images_position"].ToString() =="1")
                {
                    content +="" + cnt_sl2 + cnt_sl1 +"";
                    cnt_sl1 ="";
                    cnt_sl2 ="";
                }
                i++;
            }


您可以这样做:

1
2
3
4
5
6
7
foreach (DataGridViewRow dgr in product_list.Rows)
{
    if (dgr.Index == dgr.DataGridView.RowCount - 1)
    {
        //do something
    }
}