Reputation: 8525
I have a foreach
loop and need to execute some logic when the last item is chosen from the List
, e.g.:
foreach (Item result in Model.Results)
{
//if current result is the last item in Model.Results
//then do something in the code
}
Can I know which loop is last without using for loop and counters?
Upvotes: 334
Views: 389185
Reputation: 732
I expanded on some above answers, if it's useful to execute conditional logic on the last element, we could use a reusable generic function for lists.
Test.IfLastExample();
public static class Test
{
public static void IfLast<T>(this List<T> list, Action<T> others, Action<T> ifLast)
{
if (list.IsNullOrEmpty()) return;
for (int i = 0; i < list.Count; i++)
{
var last = i == list.Count - 1;
var element = list[i];
if (last) ifLast(element);
else others(element);
}
}
public static void IfLastExample()
{
List<int> ints = [4,1,3,5,1,4];
ints.IfLast(
others: (i) => Console.WriteLine($"others = {i}"),
ifLast: (int i) => Console.WriteLine($"Last = {i}")
);
}
}
Output:
others = 4
others = 1
others = 3
others = 5
others = 1
Last = 4
Upvotes: -2
Reputation: 137188
If you just need to do something with the last element and nothing with the rest, as opposed to something different with the last element then using LINQ will help here:
Item last = Model.Results.Last();
// do something with last
or even old school code that doesn't require LINQ:
Item last = Model.Results[Model.Results.Count - 1];
If you need to process all elements and do something different with the last element then you'd need something like:
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
}
}
Though you'd probably need to write a custom comparer to ensure that you could tell that the item was the same as the item returned by Last()
.
This approach should be used with caution as Last
may well have to iterate through the collection. While this might not be a problem for small collections, if it gets large it could have performance implications. It will also fail if the list contains duplicate items. In this cases something like this may be more appropriate:
int totalCount = Model.Results.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
}
}
Upvotes: 393
Reputation: 10459
Just store the previous value and work with it inside the loop. Then at the end the 'previous' value will be the last item, letting you handle it differently. No counting or special libraries required.
bool empty = true;
Item previousItem;
foreach (Item result in Model.Results)
{
// Alternatively, check if previousItem == null
// if your Enumerable can't contain nulls
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);
}
Upvotes: 3
Reputation: 104821
Using Last()
on certain types will loop thru the entire collection!
Meaning that if you make a foreach
and call Last()
, you looped twice! which I'm sure you'd like to avoid in big collections.
Then the solution is to use a while
loop:
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
}
}
So unless the collection type is of type IList<T>
the Last()
function will iterate thru all collection elements.
If your collection provides random access (e.g. implements IList<T>
), you can also check your item as follows.
if(collection is IList<T> list)
return collection[^1]; //replace with collection.Count -1 in pre-C#8 apps
Upvotes: 62
Reputation: 381
var last = objList.LastOrDefault();
foreach (var item in objList)
{
if (item.Equals(last))
{
}
}
Upvotes: 34
Reputation: 71591
As Chris shows, Linq will work; just use Last() to get a reference to the last one in the enumerable, and as long as you aren't working with that reference then do your normal code, but if you ARE working with that reference then do your extra thing. Its downside is that it will always be O(N)-complexity.
You can instead use Count() (which is O(1) if the IEnumerable is also an ICollection; this is true for most of the common built-in IEnumerables), and hybrid your foreach with a counter:
var i=0;
var count = Model.Results.Count();
foreach (Item result in Model.Results)
{
if (++i == count) //this is the last item
}
Upvotes: 58
Reputation: 1576
Based on @Shimmy's response, I created an extension method that is the solution that everyone wants. It is simple, easy to use, and only loops through the collection once.
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);
}
}
}
This works on any IEnumerable<T>
. Usage looks like this:
var items = new[] {1, 2, 3, 4, 5};
items.ForEachLast(i => Console.WriteLine($"{i},"), i => Console.WriteLine(i));
Output looks like:
1,
2,
3,
4,
5
Additionally, you can make this into a Select
style method. Then, reuse that extension in the ForEach
. That code looks like this:
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);
}
}
}
}
Upvotes: 1
Reputation: 17
using Linq and the 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/en/q/7213ce
Upvotes: 0
Reputation: 10715
You could just use a for loop and there is no need to add an extra if
inside the for
body:
for (int i = 0; i < Model.Results.Count - 1; i++) {
var item = Model.Results[i];
}
The -1
in the for
condition takes care of skipping the last item.
Upvotes: 0
Reputation: 10384
You can make an extension method specially dedicated to this:
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);
}
}
and you can use it like this:
foreach (Item result in Model.Results)
{
if(Model.Results.IsLast(result))
{
//do something in the code
}
}
Upvotes: -1
Reputation: 334
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++;
}
Upvotes: -2
Reputation: 13693
As Shimmy has pointed out, using Last() can be a performance problem, for instance if your collection is the live result of a LINQ expression. To prevent multiple iterations, you could use a "ForEach" extension method like this:
var elements = new[] { "A", "B", "C" };
elements.ForEach((element, info) => {
if (!info.IsLast) {
Console.WriteLine(element);
} else {
Console.WriteLine("Last one: " + element);
}
});
The extension method looks like this (as an added bonus, it will also tell you the index and if you're looking at the first 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; }
}
}
Upvotes: 15
Reputation: 527
Improving Daniel Wolf answer even further you could stack on another IEnumerable
to avoid multiple iterations and lambdas such as:
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);
}
}
The extension method implementation:
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;
}
}
}
Upvotes: 8
Reputation: 80378
How to convert foreach
to react to the last element:
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();
}
Upvotes: 0
Reputation: 903
Another way, which I didn't see posted, is to use a Queue. It's analogous to a way to implement a SkipLast() method without iterating more than necessary. This way will also allow you to do this on any number of last items.
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);
}
To call this you'd do the following:
Model.Results.ForEachAndKnowIfLast(
(result, isLast) =>
{
//your logic goes here, using isLast to do things differently for last item(s).
});
Upvotes: 0
Reputation:
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++;
}
Upvotes: -4
Reputation: 115
What about little simpler approach.
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);
Upvotes: 4
Reputation: 1
You can do like this :
foreach (DataGridViewRow dgr in product_list.Rows)
{
if (dgr.Index == dgr.DataGridView.RowCount - 1)
{
//do something
}
}
Upvotes: -4
Reputation: 14919
How about a good old fashioned for loop?
for (int i = 0; i < Model.Results.Count; i++) {
if (i == Model.Results.Count - 1) {
// this is the last item
}
}
Or using Linq and the foreach:
foreach (Item result in Model.Results)
{
if (Model.Results.IndexOf(result) == Model.Results.Count - 1) {
// this is the last item
}
}
Upvotes: 237
Reputation: 555
Making some small adjustments to the excelent code of Jon Skeet, you can even make it smarter by allowing access to the previous and next item. Of course this means you'll have to read ahead 1 item in the implementation. For performance reasons, the previous and next item are only retained for the current iteration item. It goes like this:
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<T> 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
}
}
}
Upvotes: 0
Reputation: 1988
The accepted answer will not work for duplicates in the collection. If you're set on the foreach
, you can just add your own indexing variable(s).
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++;
}
Upvotes: 2
Reputation: 772
".Last()" didnt work for me, so I had to do something like this:
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 */
);
Upvotes: -1
Reputation: 12521
The iterator implementation does not provide that. Your collection might be an IList
that is accessible via an index in O(1). In that case you can use a normal for
-loop:
for(int i = 0; i < Model.Results.Count; i++)
{
if(i == Model.Results.Count - 1) doMagic();
}
If you know the count, but cannot access via indices (thus, result is an ICollection
), you can count yourself by incrementing an i
in the foreach
's body and comparing it to the length.
All this isn't perfectly elegant. Chris's solution may be the nicest I've seen so far.
Upvotes: 6
Reputation: 4193
The best approach would probably be just to execute that step after the loop: e.g.
foreach(Item result in Model.Results)
{
//loop logic
}
//Post execution logic
Or if you need to do something to the last result
foreach(Item result in Model.Results)
{
//loop logic
}
Item lastItem = Model.Results[Model.Results.Count - 1];
//Execute logic on lastItem here
Upvotes: 4