C# 有没有办法知道我得到的是foreach循环中的最后一个元素

C# 有没有办法知道我得到的是foreach循环中的最后一个元素,c#,.net,C#,.net,我需要对集合中的最后一个元素进行特殊处理。我想知道在使用foreach循环时是否可以知道我命中了最后一个元素。我知道的唯一方法是增加一个计数器并在退出时与长度进行比较,或者在中断循环时设置一个布尔标志,loopexitedealway没有直接的方法。您必须继续缓冲下一个元素 IEnumerable<Foo> foos = ... Foo prevFoo = default(Foo); bool elementSeen = false; foreach (Foo foo in fo

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

我知道的唯一方法是增加一个计数器并在退出时与长度进行比较,或者在中断循环时设置一个布尔标志,
loopexitedealway
没有直接的方法。您必须继续缓冲下一个元素

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);
使用显示元素数量的集合(通常是
ICollection
ICollection
中的
Count
属性)时要容易得多-您可以维护计数器(或者,如果集合公开索引器,您可以使用for循环):

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

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

    else ProcessNormalItem(entry.Value);
}
如果效率不是一个问题,您可以:

Foo[] fooArray = foos.ToArray();

foreach(Foo foo in fooArray.Take(fooArray.Length - 1))
    ProcessNormalItem(foo);

ProcessLastItem(fooArray.Last());

不幸的是,我不会用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);
}
延迟执行技巧 构建一个类,该类封装要处理的值和处理函数,用于延迟执行。我们将为循环中处理的每个元素使用它的一个实例

// 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);
这种函子方法更多地使用内存,但可以防止您提前计算元素。在某些情况下,您可能会达到某种效率

较短的变通方法 如果要在以常规方式处理完所有元素后对最后一个元素应用特殊处理

// 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)

这是一个潜在的解决办法。

必须要跳过燃烧的铁环(见上文)。但是您可以直接使用枚举器(由于C#的枚举器设计有点笨拙):

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

Stack<int> stack = new ...;
stack.Peek();
IEnumerator it=foo.GetEnumerator();
for(bool hasNext=it.MoveNext();hasNext;){
字符串元素=it.Current;
hasNext=it.MoveNext();
if(hasNext){//正常处理
控制台输出写入线(元素);
}else{//最后一个元素的特殊情况处理
Console.Out.WriteLine(“最后但并非最不重要的,”+元素);
}
}
关于我在这里看到的其他方法的注释:Mitch的方法需要访问一个公开其大小的容器。J.D.的方法需要事先编写一个方法,然后通过闭包进行处理。Ani的方法将循环管理推广到了各地。John K的方法涉及创建许多其他对象,或者(第二种方法)只允许对最后一个元素进行额外的后处理,而不允许进行特殊情况处理

我不明白为什么人们不在正常循环中直接使用枚举器,正如我在这里所展示的那样


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

是否只有在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);
        }
    }
公共类ListCollection:列表
{
字符串_lastitem;
公共无效添加(字符串项)
{
//待办事项:对新项目进行特殊处理,新项目应为最后一项。
//不适用于过滤器/分拣
基础。添加(项目);
}
}

哪种类型的循环违背了foreach的目的是的,有时我会转换为传统的循环,如果意图更清楚的话
增加一个计数器并与长度进行比较…
,这正是
循环的
。@Danny Chen:我说它不是什么呢?如果你知道长度(例如,你不只是有一个枚举器),您不需要提前中断或设置任何标志,只要说
if(currentCount++==container.Count){/*last element*/}
有什么理由不使用
来进行
(它也不需要计数()
)。但是,如果最后一个字符串的处理方式与其他处理方式不兼容呢?(例如,是否有一种“好”的方法可以使用1-look-ahead重新写入此字符串?)我用另一个场景更新了我的答案,该场景使用延迟执行来实现与前瞻一样好的效果,而不必再次使用计数或迭代集合。@Mitch。我绝对不建议正常或经常使用延迟执行,但它可能是处理某些边缘情况的有用方法。这里值得注意作为武器库中的一个额外工具。第二个建议更可行,但它也仅适用于边缘情况。这应该是边缘情况的答案。或者一些代码技巧。
AsSmartEnumerable()
是Jon Skeet MiscUtil的一部分。你确定它也在莫林克?
// 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);
// 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)
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();
  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);
     }
  }
 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);
        }
    }