我需要对集合中的最后一个元素进行特殊处理。我想知道我是否可以知道在使用foreach循环时我击中了最后一个元素。
答案 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);
}
使用集合显示它们有多少元素(通常是来自Count
或ICollection
的{{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);
这种仿函数方法更多地使用了内存,但是可以防止您必须提前计算元素。在某些情况下,您可能会获得一种效率。
如果你想以常规方式处理它们之后对最后一个元素应用特殊处理....
// 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)
这是一种潜在的解决方法。