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