是否有一些罕见的语言构造我没有遇到过(比如我最近学到的一些,有些是关于Stack Overflow)在C#中得到一个表示foreach循环当前迭代的值?
例如,我目前根据具体情况做这样的事情:
int i = 0; foreach (Object o in collection) { // ... i++; }
小智.. 602
Ian Mercer在Phil Haack的博客上发布了类似的解决方案:
foreach (var item in Model.Select((value, i) => new { i, value })) { var value = item.value; var index = item.i; }
这将通过使用Linq的重载来获取item(item.value
)及其index(item.i
):Select
函数[select Select]的第二个参数表示源元素的索引.
该new { i, value }
是创建一个新的匿名对象.
ValueTuple
如果您使用的是C#7.0或更高版本,则可以避免使用堆分配:
foreach (var item in Model.Select((value, i) => ( value, i ))) { var value = item.value; var index = item.i; }
您还可以item.
通过使用自动解构来消除:
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i ))) {
- @value
}
抱歉 - 这很聪明,但它是否比在foreach之外创建索引并在每个循环中递增它更具可读性? (15认同)
使用后来的C#版本,所以你也使用元组,所以你会有这样的东西:foreach(var(item,i)在Model.Select((v,i)=>(v,i)))允许你使用元组解构直接在for循环中访问项和索引(i). (12认同)
对于Razor模板的情况,该解决方案很不错,其中模板的整洁性是一个非常重要的设计问题,并且您还希望使用枚举的每个项目的索引.但是,请记住,"包装"中的对象分配会在(不可避免的)递增整数之上增加费用(空间和时间). (8认同)
@mjsr文档是[这里](https://msdn.microsoft.com/en-us/library/bb534869(v = vs.110).aspx). (6认同)
纯粹的真棒,但在那里记录....我的意思是Select委托中的两个参数,我从来没有看到过......我找不到文档说明.....试验那个表达式我看到了这个值没有任何意义,只有位置很重要,第一个参数是元素,第二个参数是索引. (3认同)
有人可以向我解释为什么这是一个很好的答案(在撰写本文时,有450多个投票)?据我所知,它比简单地增加一个计数器更难理解,因此难以维护,它使用更多的内存,而且速度可能较慢。我想念什么吗? (3认同)
FlySwat.. 515
该foreach
是遍历实现集合IEnumerable
.它通过调用GetEnumerator
集合来执行此操作,该集合将返回一个Enumerator
.
此枚举器有一个方法和属性:
的MoveNext()
当前
Current
返回Enumerator当前所在的对象,MoveNext
更新Current
下一个对象.
索引的概念对于枚举的概念是陌生的,并且不能完成.
因此,大多数集合都可以使用索引器和for循环结构遍历.
与使用局部变量跟踪索引相比,我更倾向于在这种情况下使用for循环.
Ian Mercer在Phil Haack的博客上发布了类似的解决方案:
foreach (var item in Model.Select((value, i) => new { i, value })) { var value = item.value; var index = item.i; }
这将通过使用Linq的重载来获取item(item.value
)及其index(item.i
):Select
函数[select Select]的第二个参数表示源元素的索引.
该new { i, value }
是创建一个新的匿名对象.
ValueTuple
如果您使用的是C#7.0或更高版本,则可以避免使用堆分配:
foreach (var item in Model.Select((value, i) => ( value, i ))) { var value = item.value; var index = item.i; }
您还可以item.
通过使用自动解构来消除:
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i ))) {
- @value
}
该foreach
是遍历实现集合IEnumerable
.它通过调用GetEnumerator
集合来执行此操作,该集合将返回一个Enumerator
.
此枚举器有一个方法和属性:
的MoveNext()
当前
Current
返回Enumerator当前所在的对象,MoveNext
更新Current
下一个对象.
索引的概念对于枚举的概念是陌生的,并且不能完成.
因此,大多数集合都可以使用索引器和for循环结构遍历.
与使用局部变量跟踪索引相比,我更倾向于在这种情况下使用for循环.
最后,C#7有一个很好的语法来获取foreach
循环内的索引(即元组):
foreach (var (item, index) in collection.WithIndex()) { Debug.WriteLine($"{index}: {item}"); }
需要一点扩展方法:
public static IEnumerable<(T item, int index)> WithIndex(this IEnumerable self) => self.Select((item, index) => (item, index));
可以做这样的事情:
public static class ForEachExtensions { public static void ForEachWithIndex(this IEnumerable enumerable, Action handler) { int idx = 0; foreach (T item in enumerable) handler(item, idx++); } } public class Example { public static void Main() { string[] values = new[] { "foo", "bar", "baz" }; values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item)); } }
我不同意评论说for
在大多数情况下循环是更好的选择.
foreach
是一个有用的构造,并不是for
在所有情况下都可以通过循环替换.
例如,如果您有一个DataReader并使用foreach
它循环遍历所有记录,它会自动调用Dispose方法并关闭阅读器(然后可以自动关闭连接).因此,即使您忘记关闭阅读器,也可以更安全,因为它可以防止连接泄漏.
(当然,总是关闭读者是好的做法,但是如果你不这样做,编译器就不会抓住它 - 你不能保证你已经关闭了所有的读者,但是你可以更有可能通过获取而不会泄漏连接习惯使用foreach.)
可能存在该Dispose
方法的隐式调用有用的其他示例.
字面答案 - 警告,性能可能不如仅使用int
跟踪索引那么好.至少它比使用更好IndexOf
.
您只需要使用Select的索引重载来使用知道索引的匿名对象包装集合中的每个项目.这可以针对实现IEnumerable的任何事情来完成.
System.Collections.IEnumerable collection = Enumerable.Range(100, 10); foreach (var o in collection.OfType
使用@FlySwat的答案,我提出了这个解决方案:
//var list = new List{ 1, 2, 3, 4, 5, 6 }; // Your sample collection var listEnumerator = list.GetEnumerator(); // Get enumerator for (var i = 0; listEnumerator.MoveNext() == true; i++) { int currentItem = listEnumerator.Current; // Get current item. //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and currentItem }
您使用枚举器GetEnumerator
,然后使用for
循环循环.然而,诀窍是使循环的条件listEnumerator.MoveNext() == true
.
由于MoveNext
枚举器的方法在有下一个元素时返回true并且可以访问它,因此当我们用尽迭代的元素时,使循环条件使循环停止.
使用LINQ,C#7和System.ValueTuple
NuGet包,您可以这样做:
foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
Console.WriteLine(value + " is at index " + index);
}
您可以使用常规foreach
构造,并且能够直接访问值和索引,而不是作为对象的成员,并且仅将两个字段保留在循环的范围内.出于这些原因,我相信如果您能够使用C#7和C#7,这是最好的解决方案System.ValueTuple
.
您可以将原始枚举器包含在包含索引信息的另一个枚举器中.
foreach (var item in ForEachHelper.WithIndex(collection)) { Console.Write("Index=" + item.Index); Console.Write(";Value= " + item.Value); Console.Write(";IsLast=" + item.IsLast); Console.WriteLine(); }
这是ForEachHelper
该类的代码.
public static class ForEachHelper { public sealed class Item{ public int Index { get; set; } public T Value { get; set; } public bool IsLast { get; set; } } public static IEnumerable - > WithIndex
(IEnumerable enumerable) { Item item = null; foreach (T value in enumerable) { Item next = new Item (); next.Index = 0; next.Value = value; next.IsLast = false; if (item != null) { next.Index = item.Index + 1; yield return item; } item = next; } if (item != null) { item.IsLast = true; yield return item; } } }
使用计数器变量没有任何问题.实际上,无论你使用for
,foreach
while
还是do
一个计数器变量都必须在某处声明并递增.
如果你不确定你是否有一个适当索引的集合,请使用这个习惯用法:
var i = 0; foreach (var e in collection) { // Do stuff with 'e' and 'i' i++; }
如果你知道你的可索引集合是O(1)用于索引访问(它Array
可能用于List
(可能是文档没有说),但是不一定用于其他类型(例如LinkedList
)),否则使用这个:
// Hope the JIT compiler optimises read of the 'Count' property! for (var i = 0; i < collection.Count; i++) { var e = collection[i]; // Do stuff with 'e' and 'i' }
永远不需要IEnumerator
通过调用MoveNext()
和询问来"手动"操作Current
- foreach
为您节省特别麻烦......如果您需要跳过项目,只需continue
在循环体中使用a .
而就的完整性,这取决于你是什么做用食指(以上结构提供了极大的灵活性),您可以使用并行LINQ:
// First, filter 'e' based on 'i', // then apply an action to remaining 'e' collection .AsParallel() .Where((e,i) => /* filter with e,i */) .ForAll(e => { /* use e, but don't modify it */ }); // Using 'e' and 'i', produce a new collection, // where each element incorporates 'i' collection .AsParallel() .Select((e, i) => new MyWrapper(e, i));
我们AsParallel()
在上面使用,因为它已经是2014年了,我们希望充分利用这些多核来加快速度.此外,对于"顺序"LINQ,您只能获得一个ForEach()
扩展方法List
和Array
...并且不清楚使用它是否比执行简单更好foreach
,因为您仍然在运行单线程以获得更粗糙的语法.
这是我刚刚提出的解决这个问题的解决方案
原始代码:
int index=0; foreach (var item in enumerable) { blah(item, index); // some code that depends on the index index++; }
更新的代码
enumerable.ForEach((item, index) => blah(item, index));
扩展方法:
public static IEnumerableForEach (this IEnumerable enumerable, Action action) { var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void enumerable.Select((item, i) => { action(item, i); return unit; }).ToList(); return pSource; }
只需添加自己的索引.把事情简单化.
int i = 0; foreach (var item in Collection) { item.index = i; ++i; }
C#7最终为我们提供了一种优雅的方法:
static class Extensions { public static IEnumerable<(int, T)> Enumerate( this IEnumerable input, int start = 0 ) { int i = start; foreach (var t in input) { yield return (i++, t); } } } class Program { static void Main(string[] args) { var s = new string[] { "Alpha", "Bravo", "Charlie", "Delta" }; foreach (var (i, t) in s.Enumerate()) { Console.WriteLine($"{i}: {t}"); } } }
为什么要foreach?!
如果您使用List,最简单的方法是使用for而不是foreach .
for (int i = 0 ; i < myList.Count ; i++) { // Do something... }
或者如果你想使用foreach:
foreach (string m in myList) { // Do something... }
你可以用它来表示每个循环的khow索引:
myList.indexOf(m)
它只适用于List而不是任何IEnumerable,但在LINQ中有这样的:
IList
@Jonathan我没有说这是一个很好的答案,我只是说它只是表明它可以做他所要求的:)
@Graphain我不希望它快速 - 我不完全确定它是如何工作的,它可以在每次重复整个列表中找到一个匹配的对象,这将是比较的确认.
也就是说,List可能会保留每个对象的索引以及计数.
乔纳森似乎有更好的主意,如果他会详细说明的话?
尽管如此,更简单,更具适应性,最好只计算你在foreach中所处的位置.
int index; foreach (Object o in collection) { index = collection.indexOf(o); }
这适用于集合支持IList
.
我就是这样做的,它的简洁/简洁很好,但是如果你在循环体中做了很多obj.Value
,那么它会很快变老.
foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) { string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value); ... }
主要答案指出:
“显然,索引的概念与枚举的概念是陌生的,无法做到。”
尽管当前的C#版本是这样,但这不是概念上的限制。
MS创建新的C#语言功能可以解决此问题,并支持新的Interface IIndexedEnumerable
foreach (var item in collection with var index) { Console.WriteLine("Iteration {0} has value {1}", index, item); } //or, building on @user1414213562's answer foreach (var (item, index) in collection) { Console.WriteLine("Iteration {0} has value {1}", index, item); }
如果foreach传递了IEnumerable且无法解析IIndexedEnumerable,但是使用var index进行询问,则C#编译器可以使用IndexedEnumerable对象包装源,该对象添加了用于跟踪索引的代码。
interface IIndexedEnumerable: IEnumerable { //Not index, because sometimes source IEnumerables are transient public long IterationNumber { get; } }
为什么:
Foreach看起来更好,并且在业务应用程序中很少出现性能瓶颈
Foreach在内存上可以更有效。具有功能管道,而不是在每个步骤都转换为新集合。谁在乎它是否使用更多的CPU周期,更少的CPU缓存故障和更少的GC。
要求编码器添加索引跟踪代码,破坏美观
它非常容易实现(感谢MS)并且向后兼容
尽管此处的大多数人不是MS,但这是正确的答案,您可以游说MS来添加此类功能。您已经可以使用扩展功能构建自己的迭代器并使用元组,但是MS可以撒上语法糖以避免扩展功能
如果集合是列表,则可以使用List.IndexOf,如下所示:
foreach (Object o in collection) { // ... @collection.IndexOf(o) }