有没有办法知道我正在获取foreach循环中的最后一个元素

时间:2010-12-20 05:00:45

标签: c# .net

我需要对集​​合中的最后一个元素进行特殊处理。我想知道我是否可以知道在使用foreach循环时我击中了最后一个元素。

7 个答案:

答案 0 :(得分:5)

我知道的唯一方法是增加一个计数器并在退出时与长度进行比较,或者在断开循环时设置一个布尔标志loopExitedEarly

答案 1 :(得分:4)

没有直接方式。你必须继续缓冲下一个元素。

IEnumerable<Foo> foos = ...

Foo prevFoo = default(Foo);
bool elementSeen = false;

foreach (Foo foo in foos)
{
    if (elementSeen) // If prevFoo is not the last item...
        ProcessNormalItem(prevFoo);

    elementSeen = true;
    prevFoo = foo;
}

if (elementSeen) // Required because foos might be empty.
    ProcessLastItem(prevFoo);

或者,您可以使用基础枚举器执行相同的操作:

using (var erator = foos.GetEnumerator())
{
    if (!erator.MoveNext())
        return;

    Foo current = erator.Current;

    while (erator.MoveNext())
    {
        ProcessNormalItem(current);
        current = erator.Current;
    }

    ProcessLastItem(current);
}

使用集合显示它们有多少元素(通常是来自CountICollection的{​​{1}}属性)时,这会更容易 - 您可以维护一个计数器(或者,如果该集合公开了一个索引器,你可以使用for循环):

ICollection<T>

如果您可以使用MoreLinq,则很容易:

int numItemsSeen = 0;

foreach(Foo foo in foos)
{
   if(++numItemsSeen == foos.Count)
       ProcessLastItem(foo)

   else ProcessNormalItem(foo);
}

如果效率不是问题,你可以这样做:

foreach (var entry in foos.AsSmartEnumerable()
{
    if(entry.IsLast)
       ProcessLastItem(entry.Value)

    else ProcessNormalItem(entry.Value);
}

答案 2 :(得分:1)

不幸的是,我会用for循环编写它:

string[] names = { "John", "Mary", "Stephanie", "David" };
int iLast = names.Length - 1;
for (int i = 0; i <= iLast; i++) {
    Debug.Write(names[i]);
    Debug.Write(i < iLast ? ", " : Environment.NewLine);
}

是的,我知道String.Join :)。


我看到其他人在我打字时已经发布了类似的想法,但无论如何我都会发布:

void Enumerate<T>(IEnumerable<T> items, Action<T, bool> action) {
    IEnumerator<T> enumerator = items.GetEnumerator();
    if (!enumerator.MoveNext()) return;
    bool foundNext;
    do {
        T item = enumerator.Current;
        foundNext = enumerator.MoveNext();
        action(item, !foundNext);
    }
    while (foundNext);
}

...

string[] names = { "John", "Mary", "Stephanie", "David" };
Enumerate(names, (name, isLast) => {
    Debug.Write(name);
    Debug.Write(!isLast ? ", " : Environment.NewLine);
})

答案 3 :(得分:1)

List<int> numbers = new ....;
int last = numbers.Last();

Stack<int> stack = new ...;
stack.Peek();

更新

    var numbers = new int[] { 1, 2,3,4,5 };

    var enumerator = numbers.GetEnumerator();
    object last = null;
    bool hasElement = true;

    do
    {
        hasElement = enumerator.MoveNext();

        if (hasElement)
        {
            last = enumerator.Current;
            Console.WriteLine(enumerator.Current);
        }
        else
            Console.WriteLine("Last = {0}", last);

    } while (hasElement);

    Console.ReadKey();

答案 4 :(得分:1)

不是没有跳过燃烧的箍(见上文)。但你可以直接使用枚举器(因为C#的枚举器设计有点尴尬):

  IEnumerator<string> it = foo.GetEnumerator();
  for (bool hasNext = it.MoveNext(); hasNext; ) {
     string element = it.Current;
     hasNext = it.MoveNext();

     if (hasNext) { // normal processing
        Console.Out.WriteLine(element);
     } else { // special case processing for last element
        Console.Out.WriteLine("Last but not least, " + element);
     }
  }

我在这里看到的其他方法的注释:Mitch的方法需要访问容器,这会暴露它的大小。 J.D.的方法需要提前编写方法,然后通过闭包进行处理。 Ani的方法遍布各地的循环管理。 John K的方法涉及创建大量其他对象,或者(第二种方法)仅允许最后一个元素的附加后处理,而不是特殊情况处理。

我不明白为什么人们不会在正常循环中直接使用Enumerator,正如我在此处所示。 K.I.S.S.

使用Java迭代器更简洁,因为它们的接口使用hasNext而不是MoveNext。您可以轻松地为IEnumerable编写一个扩展方法,它为您提供了Java样式的迭代器,但除非您经常编写这种循环,否则这样做太过分了。

答案 5 :(得分:1)

是否可以在foreach循环处理时进行特殊处理,是否在添加到集合时不能这样做。如果是这种情况,请拥有自己的自定义集合

 public class ListCollection : List<string>
    {
        string _lastitem;

        public void Add(string item)
        {
            //TODO: Do  special treatment on the new Item, new item should be last one.
            //Not applicable for filter/sort
             base.Add(item);
        }
    }

答案 6 :(得分:0)

延迟执行技巧

构建一个类,用于封装要处理的值和用于延迟执行目的的处理函数。我们最终将为循环中处理的每个元素使用它的一个实例。

// functor class
class Runner {
    string ArgString {get;set;}
    object ArgContext {get;set;}

    // CTOR: encapsulate args and a context to run them in
    public Runner(string str, object context) {
        ArgString = str;
        ArgContext = context;
    }

    // This is the item processor logic. 
    public void Process() {
        // process ArgString normally in ArgContext
    }
}

foreach循环中使用您的仿函数通过一个元素实现延迟执行:

// intended to track previous item in the loop
var recent = default(Runner); // see Runner class above

// normal foreach iteration
foreach(var str in listStrings) {
    // is deferred because this executes recent item instead of current item
    if (recent != null)
        recent.Process(); // run recent processing (from previous iteration)

    // store the current item for next iteration
    recent = new Runner(str, context);
}

// now the final item remains unprocessed - you have a choice
if (want_to_process_normally)
    recent.Process(); // just like the others
else
    do_something_else_with(recent.ArgString, recent.ArgContext);

这种仿函数方法更多地使用了内存,但是可以防止您必须提前计算元素。在某些情况下,您可能会获得一种效率。

  • OR

更短的解决方法

如果你想以常规方式处理它们之后对最后一个元素应用特殊处理....

// example using strings
var recentStr = default(string);

foreach(var str in listStrings) {
    recentStr = str;
    // process str normally
}

// now apply additional special processing to recentStr (last)

这是一种潜在的解决方法。