C#foreach的改进?


9

我经常在编程过程中遇到这种情况,我想在foreach中有一个循环计数索引,并且必须创建一个整数,使用它,递增等。如果引入一个关键字是foreach内部的循环计数?它也可以在其他循环中使用。

这是否与关键字的使用背道而驰,因为编译器将不允许该关键字在循环结构中的任何地方使用?


7
您可以使用扩展方法自己进行操作,请参见Dan Finch的答案:stackoverflow.com/a/521894/866172(不是评分最高的答案,但我认为它是“更干净的”答案)
Jalayn

我想到了Perl和$ _之类的东西。我讨厌那个东西。
Yam Marcovic 2012年

2
根据我对C#的了解,它有许多基于上下文的关键字,因此这不会“反对使用关键字”。正如下面的答案中指出的那样,您可能只想使用for () 循环。
Craige 2012年

@Jalayn请张贴为答案。这是最好的方法之一。
Apoorv Khurasia 2012年

@MonsterTruck:我不认为他应该。真正的解决方案是将这个问题迁移到SO并将其关闭,作为Jalayn链接到的问题的副本。
布赖恩2012年

Answers:


54

如果需要在循环中进行循环计数,foreach为什么不使用常规for循环。该foreach环路的目的是使特定用途for循环简单。听起来您似乎遇到了这样的情况,即简单性foreach不再有益。


1
+1-好点。在常规的for循环中使用IEnumerable.Count或object.length很容易,这样可以避免计算出对象中元素的数量时出现任何问题。

11
@ GlenH7:根据实现的不同,它IEnumerable.Count可能会很昂贵(或者是不可能的,如果是一次枚举)。您需要先知道基础源是什么,然后才能安全地进行操作。
Binary Worrier 2012年

2
-1:比尔·瓦格纳在更有效的C#介绍了为什么要一直坚持下去foreachfor (int i = 0…)。他已经写了几页,但是我可以用两个词来总结-迭代中的编译器优化。好吧,这不是两个字。
Apoorv Khurasia 2012年

13
@MonsterTruck:无论比尔·瓦格纳(Bill Wagner)写什么,我已经看到了足够的情况,例如OP所描述的情况,其中for循环给出了更好的可读性代码(理论上,编译器优化通常是过早的优化)。
布朗

1
@DocBrown这不是过早的优化那么简单。我自己确定了过去的瓶颈,并使用这种方法解决了它们(但我承认我正在处理集合中的数千个对象)。希望尽快发布一个工作示例。同时,这里是Jon Skeet。[他说性能提升不会很明显,但这在很大程度上取决于对象的集合和数量]。
Apoorv Khurasia 2012年

37

您可以使用像这样的匿名类型

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

这类似于Python的enumerate功能

for i, v in enumerate(items):
    print v, i

+1哦...灯泡。我喜欢这种方法。为什么我从未想过?
Phil

17
谁愿意在C#中选择它而不是经典的for循环,显然对可读性都有一种奇怪的看法。这可能是一个巧妙的技巧,但是我不允许在生产质量代码中使用类似的方法。它比经典的for循环嘈杂,不能理解为快速。
猎鹰2012年

4
@Falcon,如果您不能使用旧的for循环(需要计数),则这是一个有效的替代方法。那是我必须解决的一些对象的收藏……
Fabricio Araujo 2012年

8
@FabricioAraujo:当然,用于循环的C#不需要计数。据我所知,它们与C的for循环相同,这意味着您可以使用任何布尔值来结束循环。例如,当MoveNext返回false时检查枚举是否结束。
Zan Lynx 2012年

1
这样做还有一个好处,就是让所有代码都可以在foreach块开始处处理增量,而不必找到它。
JeffO

13

我只是按要求在此处添加Dan Finch的答案

不要给我积分,给丹·芬奇积分:-)

解决方案是编写以下通用扩展方法:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

用法如下:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );

2
那是非常聪明的方法,但是我认为仅使用normal可以更“易读”,尤其是如果在将来,最终维护代码的人可能不是.NET中的王牌成员,并且对扩展方法的概念不太熟悉。 。我仍然在抓取这部分代码。:)
MetalMikester 2012年

1
可以对此进行争论,也可以提出最初的问题-我同意张贴者的意图,在某些情况下,foreach读起来更好,但需要一个索引,但如果不是这样,我总是不利于在语言中添加更多语法糖急需-我在自己的代码中具有完全相同的解决方案,但名称不同-strings.ApplyIndexed <T>(......)
Mark Mullin 2012年

我同意马克·穆林(Mark Mullin)的观点,你可以双方争论。我认为这是扩展方法的一种非常巧妙的用法,它适合这个问题,所以我与大家分享。
2012年

5

我对此可能是错的,但是我一直认为foreach循环的重点是简化C ++的STL时代的迭代,即

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

将此与.NET在foreach中对IEnumerable的使用进行比较

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

后者要简单得多,并且避免了许多旧的陷阱。此外,他们似乎遵循几乎相同的规则。例如,您不能在循环内更改IEnumerable的内容(如果您以STL的方式考虑它,则更有意义)。但是,for循环的逻辑目的通常不同于迭代。


4
+1:很少有人记得,foreach本质上是迭代器模式上的语法糖。
史蒂文·埃弗斯2012年

1
好吧,这里有一些区别。程序员对.NET中存在的指针进行了很深的隐藏,但是您可以编写一个非常类似于C ++示例的for循环:for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS 2012年

@KeithS,如果您意识到在.NET中接触的不是结构化类型的所有内容都是POINTER,只是使用不同的语法(。而不是->)。实际上,将引用类型传递给方法与将其作为指针传递并通过引用传递将其作为双指针传递相同。一样。至少,母语是C ++的人就是这么想的。有点像,您在学校学习西班牙语时会考虑该语言与您自己的母语在语法上的关系。
乔纳森·汉森

*值类型不是结构化类型。抱歉。
乔纳森·亨森

2

如果相关集合是IList<T>,则可以简单地for与索引一起使用:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

如果不是,则可以使用Select(),它具有一个重载,可以为您提供索引。

如果这也不适合,我认为手动维护该索引就足够简单了,它只是两行非常短的代码。为此专门创建关键字将是一个过大的选择。

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}

同样,如果您在循环内只使用一次索引,则+1,您可以执行类似的操作foo(++i)foo(i++)根据需要的索引进行操作。
工作

2

如果您只知道该集合是一个IEnumerable,但需要跟踪到目前为止已处理了多少个元素(完成后便是总数),则可以在基本的for循环中添加几行:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

您还可以将增量索引变量添加到foreach中:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

如果您想让它看起来更优雅,可以定义一个或两个扩展方法来“隐藏”这些细节:

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));

1

如果您想保持块与其他名称的冲突,也可以使用作用域设置...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

为此,我使用一个while循环。循环for也可以使用,许多人似乎更喜欢它们,但是我只喜欢使用for具有固定迭代次数的东西。IEnumerable可能是在运行时生成的,因此,我认为while这更有意义。如果愿意,可以将其称为非正式编码指南。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.