How do you get the index of the current iteration of a foreach loop?
在C中是否有一些我从未遇到过的罕见的语言构造(比如我最近学到的一些,一些堆栈溢出)来获取表示foreach循环当前迭代的值?
例如,我现在根据情况做类似的事情:
1 2 3 4 5 6 | int i = 0; foreach (Object o in collection) { // ... i++; } |
伊恩·默瑟在菲尔·哈克的博客上发布了类似的解决方案:
1 2 3 4 5 | foreach (var item in Model.Select((value, i) => new { i, value })) { var value = item.value; var index = item.i; } |
这将通过使用Linq的
the second parameter of the function [inside Select] represents the index of the source element.
如果您使用的是C 7.0或更高版本,则可以使用
1 2 3 4 5 | foreach (var item in Model.Select((value, i) => ( value, i ))) { var value = item.value; var index = item.i; } |
您还可以通过使用自动销毁来消除
1 2 3 4 5 6 | foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i ))) { <li id="item_@i">@value </li> } |
此枚举器具有方法和属性:
- MOVENTXEL()
- 电流
索引的概念与枚举的概念不同,无法实现。
因此,大多数集合都可以使用索引器和for循环构造进行遍历。
与使用局部变量跟踪索引相比,我更喜欢在这种情况下使用for循环。
可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public static class ForEachExtensions { public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> 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)); } } |
我不同意这样的观点:在大多数情况下,
例如,如果您有一个DataReader并使用
(当然,总是关闭读卡器是一种很好的做法,但如果不这样做,编译器就不会捕获它了-您不能保证已经关闭了所有的读卡器,但您可以通过养成使用foreach的习惯使其更可能不会泄漏连接。)
还有其他一些例子表明,
最后,C 7有一个很好的语法来获取
1 2 3 4 | foreach (var (item, index) in collection.WithIndex()) { Debug.WriteLine($"{index}: {item}"); } |
需要一个小的扩展方法:
1 2 | public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self.Select((item, index) => (item, index)); |
字面上的回答——警告,性能可能不如使用
您只需要使用select的索引重载,用一个知道索引的匿名对象包装集合中的每个项。这可以针对实现IEnumerable的任何内容进行。
1 2 3 4 5 6 | System.Collections.IEnumerable collection = Enumerable.Range(100, 10); foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i})) { Console.WriteLine("{0} {1}", o.i, o.x); } |
使用@flyswat的答案,我提出了这个解决方案:
1 2 3 4 5 6 7 8 9 | //var list = new List<int> { 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 } |
使用
由于枚举器的
使用linq、c_7和
1 2 3 | foreach (var (value, index) in collection.Select((v, i)=>(v, i))) { Console.WriteLine(value +" is at index" + index); } |
您可以使用常规的
可以用另一个包含索引信息的枚举器包装原始枚举器。
1 2 3 4 5 6 7 | foreach (var item in ForEachHelper.WithIndex(collection)) { Console.Write("Index=" + item.Index); Console.Write(";Value=" + item.Value); Console.Write(";IsLast=" + item.IsLast); Console.WriteLine(); } |
这是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public static class ForEachHelper { public sealed class Item<T> { public int Index { get; set; } public T Value { get; set; } public bool IsLast { get; set; } } public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable) { Item<T> item = null; foreach (T value in enumerable) { Item<T> next = new Item<T>(); 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; } } } |
使用计数器变量没有任何问题。实际上,无论您使用
因此,如果您不确定是否有适当的索引集合,请使用此成语:
1 2 3 4 5 | var i = 0; foreach (var e in collection) { // Do stuff with 'e' and 'i' i++; } |
如果您知道您的可索引集合是O(1)用于索引访问(它将用于
1 2 3 4 5 | // 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' } |
不必通过调用
为了完整起见,根据您对索引所做的操作(上面的构造提供了很大的灵活性),您可以使用Parallel Linq:
1 2 3 4 5 6 7 8 9 10 11 12 | // 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)); |
我们使用上面的
这里有一个解决这个问题的方法
原代码:
1 2 3 4 5 6 | int index=0; foreach (var item in enumerable) { blah(item, index); // some code that depends on the index index++; } |
更新代码
1 | enumerable.ForEach((item, index) => blah(item, index)); |
扩展方法:
1 2 3 4 5 6 7 8 9 10 11 | public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> 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; } |
1 2 3 4 5 | int index; foreach (Object o in collection) { index = collection.indexOf(o); } |
这对支持
它只适用于列表,不适用于任何IEnumerable,但在Linq中有:
1 2 3 4 5 6 7 8 9 10 11 12 |
@乔纳森,我没有说这是一个很好的答案,我只是说,它只是表明有可能做他要求的事情:)
@我不希望它是快速的-我不完全确定它是如何工作的,它可以通过整个列表重复每次找到一个匹配的对象,这将是一个地狱般的比较。
也就是说,list可能会保留每个对象的索引和计数。
乔纳森似乎有个更好的主意,如果他愿意详细说明的话?
不过,最好记下你的前臂在哪里,更简单,适应性更强。
C 7最终为我们提供了一种优雅的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | static class Extensions { public static IEnumerable<(int, T)> Enumerate<T>( this IEnumerable<T> 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}"); } } } |
只需添加自己的索引。保持简单。
1 2 3 4 5 6 | int i = 0; foreach (var item in Collection) { item.index = i; ++i; } |
我就是这样做的,它的简单性和简洁性很好,但是如果你在循环体
1 2 3 4 | foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) { string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value); ... } |
为什么要说?!
如果使用列表,最简单的方法是使用for而不是foreach。
1 2 3 4 | for(int i = 0 ; i < myList.Count ; i++) { // Do Something... } |
或者如果要使用foreach:
1 2 3 4 | foreach (string m in myList) { // Do something... } |
您可以使用它来获取每个循环的索引:
1 | myList.indexOf(m) |
主要答案是:
显然,索引的概念与枚举的概念不同,无法实现。
虽然这在当前的C版本中是正确的,但这不是概念上的限制。
MS创建一个新的C语言特性可以解决这个问题,同时支持一个新的接口IIndexenumerable。
1 2 3 4 5 6 7 8 9 10 | 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对象包装源,该对象添加了用于跟踪索引的代码。
1 2 3 4 5 | interface IIndexedEnumerable<T> : IEnumerable<T> { //Not index, because sometimes source IEnumerables are transient public long IterationNumber { get; } } |
为什么?
- foreach看起来更好,在业务应用程序中很少会成为性能瓶颈
- foreach可以提高内存效率。在每个步骤中都有一个函数管道,而不是转换为新的集合。谁在乎它是否使用更多的CPU周期,是否有更少的CPU缓存错误和更少的GC.Collection
- 要求编码人员添加索引跟踪代码,破坏美观
- 它很容易实现(感谢MS),并且向后兼容
虽然这里的大多数人不是微软,但这是一个正确的答案,您可以游说微软添加这样的功能。您已经可以使用扩展函数构建自己的迭代器并使用元组,但是MS可以撒上语法甜头以避免扩展函数
最好使用关键字
1 2 3 4 5 6 7 8 | int i=-1; foreach (Object o in collection) { ++i; //... continue; //<--- safe to call, index will be increased //... } |
如果集合是列表,则可以使用list.indexof,如:
1 2 3 4 5 | foreach (Object o in collection) { // ... @collection.IndexOf(o) } |
我对这个问题的解决方案是扩展方法
http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/src/utilities/extensions/enumerableextensions.cs
像它一样使用它
1 2 3 4 5 |
出于兴趣,Phil Haack在Razor模板化的代表(http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx)的背景下写了一个这样的例子。
实际上,他编写了一个扩展方法,该方法将迭代包装在"iterateditem"类中(见下文),允许在迭代期间访问索引和元素。
1 2 3 4 5 6 7 8 9 | public class IndexedItem<TModel> { public IndexedItem(int index, TModel item) { Index = index; Item = item; } public int Index { get; private set; } public TModel Item { get; private set; } } |
但是,如果您在非Razor环境中执行单个操作(即可以作为lambda提供的操作),那么在非Razor环境中,这将不会是for/foreach语法的可靠替代。
我不认为这会很有效率,但它是有效的:
1 2 3 | @foreach (var banner in Model.MainBanners) { @Model.MainBanners.IndexOf(banner) } |
我在林肯帕德建造了这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var listOfNames = new List<string>(){"John","Steve","Anna","Chris"}; var listCount = listOfNames.Count; var NamesWithCommas = string.Empty; foreach (var element in listOfNames) { NamesWithCommas += element; if(listOfNames.IndexOf(element) != listCount -1) { NamesWithCommas +=","; } } NamesWithCommas.Dump(); //LINQPad method to write to console. |
您也可以使用
1 | var joinResult = string.Join(",", listOfNames); |
您可以这样编写循环:
1 2 3 4 5 | var s ="ABCDEFG"; foreach (var item in s.GetEnumeratorWithIndex()) { System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index); } |
在添加以下结构和扩展方法之后。
结构和扩展方法封装了可枚举的。请选择功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public struct ValueWithIndex<T> { public readonly T Value; public readonly int Index; public ValueWithIndex(T value, int index) { this.Value = value; this.Index = index; } public static ValueWithIndex<T> Create(T value, int index) { return new ValueWithIndex<T>(value, index); } } public static class ExtensionMethods { public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable) { return enumerable.Select(ValueWithIndex<T>.Create); } } |
像这样的怎么样?注意,如果myEnumeratable为空,myDelimitedString可能为空。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | IEnumerator enumerator = myEnumerable.GetEnumerator(); string myDelimitedString; string current = null; if( enumerator.MoveNext() ) current = (string)enumerator.Current; while( null != current) { current = (string)enumerator.Current; } myDelimitedString += current; if( enumerator.MoveNext() ) myDelimitedString += DELIMITER; else break; } |
我刚刚遇到了这个问题,但是在我的案例中,思考这个问题给出了最佳的解决方案,与预期的解决方案无关。
这可能是一个很常见的情况,基本上,我正在从一个源列表中读取数据,并在目标列表中基于它们创建对象,但是,我必须首先检查源项是否有效,并希望返回任何错误的行。乍一看,我想将索引放入当前属性的对象枚举器中,但是,在复制这些元素时,我仍然从当前目标隐式地知道当前索引。显然,它取决于目标对象,但对我来说,它是一个列表,而且很可能实现ICollection。
即
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var destinationList = new List<someObject>(); foreach (var item in itemList) { var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries); if (stringArray.Length != 2) { //use the destinationList Count property to give us the index into the stringArray list throw new Exception("Item at row" + (destinationList.Count + 1) +" has a problem."); } else { destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]}); } } |
我认为,不一定总是适用的,但常常足够值得一提。
不管怎样,关键是有时在你的逻辑中已经有了一个不明显的解决方案…
除非集合可以通过某种方法返回对象的索引,否则唯一的方法是使用类似于示例中的计数器。
但是,在处理索引时,唯一合理的解决方法是使用for循环。其他任何东西都会带来代码复杂性,更不用说时间和空间复杂性了。
我不相信有一种方法可以获得foreach循环当前迭代的值。数数自己,似乎是最好的方法。
我可以问一下,你为什么想知道?
看来你最有可能做的是以下三件事之一:
1)从集合中获取对象,但在本例中,您已经拥有了它。
2)对对象进行计数以备日后处理…集合具有可供使用的Count属性。
3)根据对象在循环中的顺序设置该对象的属性……尽管在将该对象添加到集合中时可以轻松设置该属性。
我不知道你是怎么处理基于这个问题的索引信息的。但是,在C中,您通常可以调整IEnumerable.Select方法以从所需的内容中获取索引。例如,对于一个值是奇数还是偶数,我可能使用类似的方法。
1 2 3 4 | string[] names = {"one","two","three" }; var oddOrEvenByName = names .Select((name, index) => new KeyValuePair<string, int>(name, index % 2)) .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); |
这会给你一本字典,名字是奇数(1)还是偶数(0)。
这并不能回答您的特定问题,但它确实为您的问题提供了解决方案:使用for循环在对象集合中运行。然后您将得到正在处理的当前索引。
1 2 3 4 5 | // Untested for (int i = 0; i < collection.Count; i++) { Console.WriteLine("My index is" + i); } |
我想从理论上更深入地讨论这个问题(因为它已经有了足够的实际答案)
.NET有一个非常好的数据组抽象模型(也称为集合)
- 在最顶层,最抽象的是,您有一个
IEnumerable ,它只是一组您可以枚举的数据。不管你如何枚举,你只需要枚举一些数据就行了。这个枚举是由一个完全不同的对象完成的,一个IEnumerator 。
这些接口定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | // // Summary: // Exposes an enumerator, which supports a simple iteration over a non-generic collection. public interface IEnumerable { // // Summary: // Returns an enumerator that iterates through a collection. // // Returns: // An System.Collections.IEnumerator object that can be used to iterate through // the collection. IEnumerator GetEnumerator(); } // // Summary: // Supports a simple iteration over a non-generic collection. public interface IEnumerator { // // Summary: // Gets the element in the collection at the current position of the enumerator. // // Returns: // The element in the collection at the current position of the enumerator. object Current { get; } // // Summary: // Advances the enumerator to the next element of the collection. // // Returns: // true if the enumerator was successfully advanced to the next element; false if // the enumerator has passed the end of the collection. // // Exceptions: // T:System.InvalidOperationException: // The collection was modified after the enumerator was created. bool MoveNext(); // // Summary: // Sets the enumerator to its initial position, which is before the first element // in the collection. // // Exceptions: // T:System.InvalidOperationException: // The collection was modified after the enumerator was created. void Reset(); } |
正如您可能注意到的,
IEnumerator 接口不"知道"索引是什么,它只知道它当前指向的元素以及如何移动到下一个元素。现在的诀窍是:
foreach 把每个输入集合都看作一个IEnumerable ,即使它是一个更具体的实现,比如IList (它继承自IEnumerable ),它只会看到抽象的接口IEnumerable 。实际上,
foreach 正在做的是,在集合上调用GetEnumerator ,并调用MoveNext ,直到返回false。所以这里有一个问题,你想在一个抽象概念"可枚举"上定义一个具体的概念"索引",内置的
foreach 构造并没有给你这个选项,所以你唯一的方法就是自己定义它,或者通过你最初做的(手工创建计数器)或者仅仅使用IEnumerator 的一个实现来识别对索引进行索引并实现一个识别该自定义实现的foreach 构造。
我个人会创建这样的扩展方法
1 2 3 4 5 6 7 8 9 10 11 12 | public static class Ext { public static void FE<T>(this IEnumerable<T> l, Action<int, T> act) { int counter = 0; foreach (var item in l) { act(counter, item); counter++; } } } |
像这样使用
1 2 3 4 5 | var x = new List<string>() {"hello","world" }; x.FE((ind, ele) => { Console.WriteLine($"{ind}: {ele}"); }); |
这也避免了在其他答案中看到任何不必要的分配。
这里是这个问题的另一个解决方案,重点是尽可能使语法接近标准的
如果你想让你的视图在MVC中看起来漂亮整洁,这种构造是很有用的。例如,不要用通常的方式(很难很好地格式化)编写:
1 2 3 4 5 6 7 | <%int i=0; foreach (var review in Model.ReviewsList) { %> "> <%:review.Title%> <%i++; } %> |
你可以这样写:
1 2 3 4 5 | <%foreach (var review in Model.ReviewsList.WithIndex()) { %> "> <%:review.Title%> <%} %> |
我已经编写了一些辅助方法来启用此功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public static class LoopHelper { public static int Index() { return (int)HttpContext.Current.Items["LoopHelper_Index"]; } } public static class LoopHelperExtensions { public static IEnumerable<T> WithIndex<T>(this IEnumerable<T> that) { return new EnumerableWithIndex<T>(that); } public class EnumerableWithIndex<T> : IEnumerable<T> { public IEnumerable<T> Enumerable; public EnumerableWithIndex(IEnumerable<T> enumerable) { Enumerable = enumerable; } public IEnumerator<T> GetEnumerator() { for (int i = 0; i < Enumerable.Count(); i++) { HttpContext.Current.Items["LoopHelper_Index"] = i; yield return Enumerable.ElementAt(i); } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } |
在非Web环境中,您可以使用
这本质上是一个全局变量,因此不能嵌套多个WITHINDEX循环,但这不是这个用例中的主要问题。