如何获取 foreach 循环当前迭代的索引?

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

答案

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}
foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}
<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

foreach用于迭代实现IEnumerable集合。它通过在集合上调用GetEnumerator来执行此操作,该集合将返回Enumerator

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

  • MoveNext()
  • 当前

Current返回 Enumerator Current的对象, MoveNext Current更新为下一个对象。

索引的概念与枚举的概念无关,因此无法做到。

因此,大多数集合都可以使用索引器和 for 循环结构遍历。

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

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index));
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));
    }
}

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

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

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

(确保始终关闭读者是一个好习惯,但是如果您不这样做,则编译器将不会捕获它 - 您不能保证已关闭所有读者,但是可以通过以下方法使自己更有可能不会泄漏连接:养成使用 foreach 的习惯。)

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

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);
}
//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
}
foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}
foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}
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;
        }            
    }
}
var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}
// 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'
}
// 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));