简体   繁体   中英

How do I know if I'm iterating on the last item of the collection?

I want to do something different on the last KeyValuePair of the Dictionary I'm iterating on.

For Each item In collection
    If ItsTheLastItem
        DoX()
    Else
        DoY()
    End If
Next 

Is this possible?

Re-Edit: I'm not using a Dictionary's values, I'm actually using a List of KeyValuePair s. I've converted them and didn't notice later, dumb me.

Unless you want to implement the foreach yourself, use a counter (C# code):

int count = collection.Count;
int counter = 0;

foreach(var item in collection)
{
  counter++;
  if(counter == count)
    DoX();
  else
    DoY();
}

Please note that this will only work with non-streamed IEnumerable<T> , also, depending on the implementation, Count would cause the collection to be walked twice.

I would make your own extension method. This implementation here guarantees you only walk the collection once.

static class IEnumerableExtensions {
    public static void ForEachExceptTheLast<T>(
        this IEnumerable<T> source,
        Action<T> usualAction,
        Action<T> lastAction
    ) {
        var e = source.GetEnumerator();
        T penultimate;
        T last;
        if (e.MoveNext()) {
            last = e.Current;
            while(e.MoveNext()) {
                penultimate = last;
                last = e.Current;
                usualAction(penultimate);
            }
            lastAction(last);
        }
    }
}    

Usage:

Enumerable.Range(1, 5)
          .ForEachExceptTheLast(
              x => Console.WriteLine("Not last: " + x),
              x => Console.WriteLine("Last: " + x)
);

Output:

Not last: 1
Not last: 2
Not last: 3
Not last: 4
Last: 5

Why not use:

List<string> list = new List<string>();
// add items

foreach (string text in list)
{
    if (text.Equals(list[list.Count - 1]))
    {
        // last item
    }
    else
    {
        // not last item
    }
}

将集合转换为列表(如LINQ的ToList())并使用for(int i = 0)...循环进行迭代。

You can't do this naturally with an IEnumerable<T> as you won't know whether there are any more elements until you try to get to the next one.

However, in my MiscUtil library I have something called SmartEnumerable which reads one item in advance, in order to provide the following properties:

  • IsFirst
  • IsLast
  • Index
  • Value

See this page for usage instructions. For example:

For Each item In collection.ToSmartEnumerable()
    If item.IsFirst
        DoX()
    Else
        DoY()
    End If
Next 

You'd need to use item.Value to get at the value of the current item.

One point about iterating over dictionaries though - they're not actually in any order. So while you can iterate and there will be a first entry and a last entry, you shouldn't assume that they'll be the first entry added and the last entry added respectively. That may not be a problem for your use case, but you ought to be aware of it.

I just had an idea that doesn't use a counter.

For Each item In collection.GetRange(0, collection.Count - 1)
    DoY(item)
Next
DoX(collection.Last)

Edit: sorry this is for a List, I had converted it before and intellisense gave me List methods instead of Dictionary methods.

I created a solution to build an IP String from 4 octets based on @CamiloMartin answer above.

 String sIP;
 StringBuilder sb = new StringBuilder();
 List<String> lstOctets= new List<string>{ usrIP1.Text, usrIP2.Text, usrIP3.Text, usrIP4.Text };
 foreach (String Octet in lstOctets.GetRange(0,lstOctets.Count - 1))
 {
    sb.Append(string.Format("{0:d}.", Octet));
 }
 if (lstOctets.Count == 4)
    sb.Append(string.Format("{0:d}", lstOctets[lstOctets.Count - 1]));
 else
     sb.Append(string.Format("{0:d}.0", lstOctets[lstOctets.Count - 1]));
 sIP = sb.ToString();
        foreach (var item in collection.Reverse().Skip(1))
        {

        }
        // do here the stuff related to list.LastOrDefaut()

Another approach is to maintain the iteration index and when hit the collection.Length-2 or collection.Count()-2 then stop the loop and do whatever you want with last element.

But as @BrokenGlass said beware of side effects that it may has on your application.

And generally you must use Linq with special care. For example every time you iterate on a linq to sql collection, you execute the query again, so it's much better to run it once eagerly by simply calling .ToList() and use that in memory collection as much as you want.

With an Dictionary you have multiple options

Dim dictionary as new Dictionary(of String, MyObject)
For Each item in dictionary
    // item is KeyValue pair
Next

For Each item in dictionary.Keys
    // item is String
Next

For Each item in dictionary.Keys
    // item is MyObject
Next

I would suggest you itterate over the ValueCollection and either use a counter or do a Equals comparison between the current and the last item (which will probably be slower but you save a counter variable ;) Don't forget to add a check of an empty list.

For Each item in dictionary.Values

    If dictionary.Count > 0 AndAlso _
        item.Equals(dictionary.Values(dictionary.Count - 1)) Then
        Console.WriteLine("Last Item")
    Else
        Console.WriteLine("Some other Item")
    End If

Next

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM