IEnumerable foreach, do something different for the last element

旧时模样 提交于 2020-01-01 08:32:45

问题


I have an IEnumerable<T>. I want to do one thing for each item of the collection, except the last item, to which I want to do something else. How can I code this neatly? In Pseudocode

foreach (var item in collection)
{
    if ( final )
    {
        g(item)
    }
    else
    {
        f(item)
    }
}

So if my IEnumerable were Enumerable.Range(1,4) I'd do f(1) f(2) f(3) g(4). NB. If my IEnumerable happens to be length 1, I want g(1).

My IEnumerable happens to be kind of crappy, making Count() as expensive as looping over the whole thing.


回答1:


Since you mention IEnumerable[<T>] (not IList[<T>] etc), we can't rely on counts etc: so I would be tempted to unroll the foreach:

using(var iter = source.GetEnumerator()) {
    if(iter.MoveNext()) {
        T last = iter.Current;
        while(iter.MoveNext()) {
            // here, "last" is a non-final value; do something with "last"
            last = iter.Current;
        }
        // here, "last" is the FINAL one; do something else with "last"
    }
}

Note the above is technically only valid for IEnuemerable<T>; for non-generic, you'd need:

var iter = source.GetEnumerator();
using(iter as IDisposable) {
    if(iter.MoveNext()) {
        SomeType last = (SomeType) iter.Current;
        while(iter.MoveNext()) {
            // here, "last" is a non-final value; do something with "last"
            last = (SomeType) iter.Current;
        }
        // here, "last" is the FINAL one; do something else with "last"
    }
}



回答2:


Similar to Marc's answer, but you could write an extension method to wrap it up.

public static class LastEnumerator
{
    public static IEnumerable<MetaEnumerableItem<T>> GetLastEnumerable<T>(this IEnumerable<T> blah)
    {
        bool isFirst = true;
        using (var enumerator = blah.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                bool isLast;
                do
                {
                    var current = enumerator.Current;
                    isLast = !enumerator.MoveNext();
                    yield return new MetaEnumerableItem<T>
                        {
                            Value = current,
                            IsLast = isLast,
                            IsFirst = isFirst
                        };
                    isFirst = false;
                } while (!isLast);
            }
        }

    }
}

public class MetaEnumerableItem<T>
{
    public T Value { get; set; }
    public bool IsLast { get; set; }
    public bool IsFirst { get; set; }
}

Then call it like so:

foreach (var row in records.GetLastEnumerable())
{
    output(row.Value);
    if(row.IsLast)
    {
        outputLastStuff(row.Value);
    }
}



回答3:


If you want to do this as efficiently as possible there is no other choice than effectively looking at not only the current but also the "next" or "previous" item, so you can defer the decision of what to do after you have that information. For example, assuming T is the type of items in the collection:

if (collection.Any()) {
    var seenFirst = false;
    T prev = default(T);
    foreach (var current in collection) {
        if (seenFirst) Foo(prev);
        seenFirst = true;
        prev = current;
    }
    Bar(prev);
}

See it in action.




回答4:


not 100% sure I like this but you could always delay the use of item until you've moved one step in to the IEnumerable array, that way when you get to the end you've not used the last entry.

it avoids having to force a count on the enumerator.

object item = null;
foreach (var a in items)
{
  // if item is set then we can use it.
  if (item != null)
  {
      // not final item
      f(item);
  }
  item = a;
}

// final item.
g(item);



回答5:


I wouldn't really recommend it, but I guess you could do something like this...

object m_item = notPartOfListFlag = new object();
foreach(var item in enumerator){
   if(m_item != notPartOfListFlag)
   {
      //do stuff to m_item;
   }
   m_item = item;
}
//do stuff to last item aka m_item;

But I would try to use some kind of collection that exposes the position of the items in the list, then use

if(collection.IndexOf(item) == collection.Count-1) do stuff


来源:https://stackoverflow.com/questions/10735727/ienumerable-foreach-do-something-different-for-the-last-element

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!