如何获得Foreach循环当前迭代的索引?

在C#中是否有一些我没有遇到过的罕见语言结构(比如我最近学到的一些,一些在Stack Overflow上)来获取表示Foreach循环当前迭代的值?

例如,我现在根据情况做这样的事情:

int i = 0;foreach (Object o in collection){// ...i++;}
1345817 次浏览

除非您的集合可以通过某种方法返回对象的索引,否则唯一的方法是使用像您的示例中那样的计数器。

但是,在使用索引时,唯一合理的解决方案是使用for循环。其他任何东西都会引入代码复杂性,更不用说时间和空间复杂性了。

我不相信有一种方法可以得到Foreach循环当前迭代的值。自己计数,似乎是最好的方法。

我能问问你为什么想知道吗?

看起来你最有可能做三件事之一:

1)从集合中获取对象,但在这种情况下您已经拥有它。

2)计数对象以供以后的后期处理……集合有一个Count属性,您可以使用它。

3)根据对象在循环中的顺序在对象上设置属性……尽管您可以在将对象添加到集合时轻松设置。

foreach用于迭代实现#1的集合。它通过在集合上调用#2来做到这一点,这将返回#3

这个枚举器有一个方法和一个属性:

  • MoveNext()
  • Current

Current返回枚举器当前所在的对象,MoveNextCurrent更新到下一个对象。

索引的概念与枚举的概念是不同的,因此无法实现。

正因为如此,大多数集合都可以使用索引器和for循环构造进行遍历。

与使用局部变量跟踪索引相比,我更喜欢在这种情况下使用for循环。

可以这样做:

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));}}

它只适用于List而不是任何IENumable,但在LINQ中是这样的:

IList<Object> collection = new List<Object> {new Object(),new Object(),new Object(),};
foreach (Object o in collection){Console.WriteLine(collection.IndexOf(o));}
Console.ReadLine();

@乔纳森我没有说这是一个很好的答案,我只是说这只是表明有可能做他要求的事情:)

@Graphain我不认为它会很快-我不完全确定它是如何工作的,它可以每次重复整个列表以找到匹配的对象,这将是一个非常好的比较。

也就是说,List可能会保留每个对象的索引以及计数。

乔纳森似乎有一个更好的主意,如果他能详细说明?

不过,最好只是计算一下你在前面的位置,更简单,适应性更强。

字面答案--警告,性能可能不如仅仅使用int来跟踪索引。至少它比使用IndexOf要好。

您只需要使用选择的索引重载来用一个知道索引的匿名对象包装集合中的每个项目。这可以针对任何实现IENumable的东西来完成。

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);}

我不同意for循环在大多数情况下是更好的选择的评论。

foreach是一个有用的构造,在所有情况下都不能被for循环替换。

例如,如果您有一个DataReader并使用foreach循环所有记录,它会自动调用处置方法并关闭阅读器(然后可以自动关闭连接)。因此,这更安全,因为即使您忘记关闭阅读器,它也可以防止连接泄漏。

(当然,总是关闭阅读器是一种很好的做法,但如果你不这样做,编译器就不会抓住它——你不能保证你已经关闭了所有阅读器,但你可以通过养成使用foreach的习惯,让它更有可能不会泄漏连接。

可能还有Dispose方法的隐式调用有用的其他示例。

这是我刚想出的解决这个问题的方法

原始代码:

int index=0;foreach (var item in enumerable){blah(item, index); // some code that depends on the indexindex++;}

更新代码

enumerable.ForEach((item, index) => blah(item, index));

扩展方法:

    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 voidenumerable.Select((item, i) =>{action(item, i);return unit;}).ToList();
return pSource;}
int index;foreach (Object o in collection){index = collection.indexOf(o);}

这将适用于支持IList的集合。

您可以用另一个包含索引信息的枚举器包装原始枚举器。

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<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;}}}

最好像这样使用关键字continue安全构造

int i=-1;foreach (Object o in collection){++i;//...continue; //<--- safe to call, index will be increased//...}

我就是这样做的,它的简单性/简洁性很好,但是如果你在循环主体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);...}

我对这个问题的解决方案是扩展方法WithIndex()

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

用它像

var list = new List<int> { 1, 2, 3, 4, 5, 6 };
var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));

像这样的东西怎么样?请注意,如果myEnumable为空,则myD可能为空。

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;elsebreak;}

我只是遇到了这个问题,但在我的情况下,围绕问题进行思考给出了最佳解决方案,与预期的解决方案无关。

这可能是一个很常见的情况,基本上,我从一个源列表读取并在目标列表中基于它们创建对象,然而,我必须首先检查源项是否有效,并希望返回任何错误的行。乍一看,我想将索引放入当前属性的对象的枚举器中,然而,当我复制这些元素时,我隐式地知道当前索引无论如何都来自当前目的地。显然这取决于你的目标对象,但对我来说这是一个List,很可能它将实现ICollection。

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 listthrow new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");}else{destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});}}

不总是适用的,但经常值得一提,我想。

不管怎样,关键是有时在你的逻辑中已经有了一个不明显的解决方案…

出于兴趣,Phil Haack刚刚在Razor模板化委托的上下文中写了一个示例(http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx

实际上,他编写了一个扩展方法,将迭代包装在一个“IteratedItem”类(见下文)中,允许在迭代期间访问索引和元素。

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; }}

但是,如果您正在执行单个操作(即可以作为lambda提供的操作),那么在非Razor环境中这将是很好的,但它不会在非Razor上下文中取代for/foreach语法。

我不确定你想根据这个问题对索引信息做什么。但是,在C#中,你通常可以调整IENumable.选择方法来从你想要的任何内容中获取索引。例如,我可能会使用这样的东西来判断一个值是奇数还是偶数。

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)为您提供一个字典。

Ian Mercer在Phil Haack的博客上发布了类似的解决方案:

foreach (var item in Model.Select((value, i) => new { i, value })){var value = item.value;var index = item.i;}

这将通过使用这个过载的LINQ的#2获得项目(item.value)及其索引(item.i):

函数的第二个参数[在选择中]表示源元素的索引。

new { i, value }正在创建一个新的匿名对象

如果您使用的是C#7.0或更高版本,可以通过使用ValueTuple来避免堆分配:

foreach (var item in Model.Select((value, i) => ( value, i ))){var value = item.value;var index = item.i;}

您还可以使用自动解构来消除item.

foreach (var (value, i) in Model.Select((value, i) => ( value, i ))){// Access `value` and `i` directly here.}

我不认为这应该很有效,但它有效:

@foreach (var banner in Model.MainBanners) {@Model.MainBanners.IndexOf(banner)}

这是这个问题的另一个解决方案,重点是保持语法尽可能接近标准foreach

如果你想让你的视图在MVC中看起来漂亮干净,这种结构很有用。例如,不要用通常的方式编写(很难很好地格式化):

 <%int i=0;foreach (var review in Model.ReviewsList) { %><div id="review_<%=i%>"><h3><%:review.Title%></h3></div><%i++;} %>

你可以这样写:

 <%foreach (var review in Model.ReviewsList.WithIndex()) { %><div id="review_<%=LoopHelper.Index()%>"><h3><%:review.Title%></h3></div><%} %>

我写了一些辅助方法来实现这一点:

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环境中,您可以使用static而不是HttpContext.Current.Items

这本质上是一个全局变量,因此您不能嵌套多个With Index循环,但这在这个用例中不是一个主要问题。

使用@FlySwat的答案,我想出了这个解决方案:

//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}

您使用GetEnumerator获取枚举器,然后使用for循环进行循环。然而,诀窍是使循环的条件为listEnumerator.MoveNext() == true

由于枚举器的MoveNext方法如果有下一个元素并且可以访问它,则返回true,因此当我们用完要迭代的元素时,循环条件会使循环停止。

我在LINQPad中创建了这个:

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.

你也可以使用string.join

var joinResult = string.Join(",", listOfNames);

使用计数器变量没有错。事实上,无论您使用forforeachwhile还是do,计数器变量都必须在某个地方声明并递增。

因此,如果您不确定是否有适当索引的集合,请使用这个习语:

var i = 0;foreach (var e in collection) {// Do stuff with 'e' and 'i'i++;}

否则使用这个,如果你知道,你的转位集合是O(1)索引访问(这将是Array和可能List<T>(留档没有说),但不一定为其他类型(如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'}

永远不需要通过调用MoveNext()来“手动”操作IEnumerator,并且询问Current-foreach可以节省您的特殊麻烦……如果您需要跳过项目,只需在循环主体中使用continue

为了完整性,根据你的索引(上面的结构提供了很大的灵活性),你可以使用并行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,你只能在#2和#3上获得#1扩展方法…并且不清楚使用它是否比执行简单的foreach更好,因为您仍然在运行单线程以获得更丑陋的语法。

如果集合是列表,则可以使用List. IndexOf,如下所示:

foreach (Object o in collection){// ...@collection.IndexOf(o)}

这并没有回答你的具体问题,但它确实为你提供了一个解决问题的方法:使用for循环来运行对象集合。然后你将拥有你正在处理的当前索引。

// Untestedfor (int i = 0; i < collection.Count; i++){Console.WriteLine("My index is " + i);}

为什么Foreach?!

最简单的方法是使用进行而不是Foreach如果您使用List

for (int i = 0 ; i < myList.Count ; i++){// Do something...}

或者如果你想使用Foreach:

foreach (string m in myList){// Do something...}

你可以用它来知道每个循环的索引:

myList.indexOf(m)

你可以这样写你的循环:

var s = "ABCDEFG";foreach (var item in s.GetEnumeratorWithIndex()){System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);}

添加以下struct和扩展方法后。

struct和扩展名方法封装了枚举。选择功能。

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);}}

最后,C#7有一个不错的语法来获取foreach循环(即元组)内的索引:

foreach (var (item, index) in collection.WithIndex()){Debug.WriteLine($"{index}: {item}");}

需要一点扩展方法:

using System.Collections.Generic;
public static class EnumExtension {public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)=> self.Select((item, index) => (item, index));}

这个答案:游说C#语言团队获得直接的语言支持。

领先的答案是:

显然,索引的概念与枚举,并且无法执行。

虽然当前的C#语言版本(2020)是如此,但这不是概念性的CLR/语言限制,可以做到。

Microsoft C#语言开发团队可以通过添加对新接口IIndexedENumable的支持来创建新的C#语言功能

foreach (var item in collection with var index){Console.WriteLine("Iteration {0} has value {1}", index, item);}
//or, building on @user1414213562's answerforeach (var (item, index) in collection){Console.WriteLine("Iteration {0} has value {1}", index, item);}

如果使用foreach ()并且存在with var index,则编译器期望项集合声明IIndexedEnumerable接口。如果接口不存在,编译器可以用IndexedENumable对象填充源,这会添加跟踪索引的代码。

interface IIndexedEnumerable<T> : IEnumerable<T>{//Not index, because sometimes source IEnumerables are transientpublic long IterationNumber { get; }}

稍后,CLR可以更新为具有内部索引跟踪,仅在指定with关键字并且源不直接实现IIndexedEnumerable时使用

为什么:

  • Foreach看起来更好,在业务应用程序中,Foreach循环很少成为性能瓶颈
  • Foreach可以更有效地利用内存。拥有一个函数管道,而不是在每一步转换为新集合。当CPU缓存故障和垃圾回收更少时,谁在乎它是否使用更多的CPU周期?
  • 要求编码器添加索引跟踪代码,破坏了美观
  • 它很容易实现(请Microsoft)并且向下兼容

虽然这里的大多数人都不是微软员工,但这是一个的正确答案,你可以游说微软添加这样的功能。你已经可以用扩展函数和使用元组构建自己的迭代器了,但微软可以撒上语法糖来避免扩展功能

C#7最终给了我们一个优雅的方法来做到这一点:

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}");}}}

使用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和System.ValueTuple,我相信这是最好的解决方案。

只需添加您自己的索引。保持简单。

int i = -1;foreach (var item in Collection){++i;item.index = i;}

我想从理论上讨论这个问题(因为它已经有了足够的实际答案)

. net对数据组(又名集合)有一个非常好的抽象模型

  • 在最顶部,也是最抽象的,你有一个IEnumerable它只是一组你可以枚举的数据。你如何枚举并不重要,只是你可以枚举一些数据。这个枚举是由一个完全不同的对象完成的,一个IEnumerator

这些接口定义如下:

//// 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<T>(继承自IEnumerable),它也只会看到抽象接口IEnumerable

  • foreach实际上正在做的是在集合上调用GetEnumerator,并调用MoveNext直到它返回false。

  • 所以这里的问题是,你想定义一个具体的概念“索引”在一个抽象的概念“枚举”,内置的foreach构造不给你这个选项,所以你唯一的方法是自己定义它,要么通过你最初做什么(手动创建一个计数器)或只是使用IEnumerator的实现,识别索引和实现foreach构造,识别自定义实现。

我个人会创建一个像这样的扩展方法

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++;}}}

并像这样使用它

var x = new List<string>() { "hello", "world" };x.FE((ind, ele) =>{Console.WriteLine($"{ind}: {ele}");});

这也避免了在其他答案中看到的任何不必要的分配。

通过这种方式,您可以使用LINQ使用索引和值:

ListValues.Select((x, i) => new { Value = x, Index = i }).ToList().ForEach(element =>{// element.Index// element.Value});
// using foreach loop how to get index number:    
foreach (var result in results.Select((value, index) => new { index, value })){// do something}