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

Mat*_*ell 861 c# foreach

是否有一些罕见的语言构造我没有遇到过(比如我最近学到的一些,有些是关于Stack Overflow)在C#中得到一个表示foreach循环当前迭代的值?

例如,我目前根据具体情况做这样的事情:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
Run Code Online (Sandbox Code Playgroud)

小智 602

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

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}
Run Code Online (Sandbox Code Playgroud)

这将通过使用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;
}
Run Code Online (Sandbox Code Playgroud)

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

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>
Run Code Online (Sandbox Code Playgroud)

  • 抱歉 - 这很聪明,但它是否比在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认同)
  • @RichN Linq `.Select( T item, Int32 index )` 方法现在在大多数 C# 程序员中众所周知。只有当您还不熟悉 Linq 及其函数式编程风格时,才会感到困惑。 (5认同)
  • 纯粹的真棒,但在那里记录....我的意思是Select委托中的两个参数,我从来没有看到过......我找不到文档说明.....试验那个表达式我看到了这个值没有任何意义,只有位置很重要,第一个参数是元素,第二个参数是索引. (3认同)
  • 有人可以向我解释为什么这是一个很好的答案(在撰写本文时,有450多个投票)?据我所知,它比简单地增加一个计数器更难理解,因此难以维护,它使用更多的内存,而且速度可能较慢。我想念什么吗? (3认同)
  • 与递增计数器相比,有人对 `tuple` 版本进行了任何性能和内存使用测试吗?我相信其他人想知道性能和内存是否是他们编码要求的一个因素。 (3认同)
  • @RichN B/c `for` 和 `foreach` 循环如今并不“酷”(/s)。链接是。问题是 LINQ _故意_没有 `forEach` (正如 [Eric Lippert 精美地解释的那样](https://docs.microsoft.com/en-us/archive/blogs/ericlippert/foreach-vs-foreach)) 。这就是 b/c foreach 仅用于副作用,打破了链接隐喻。LINQ 称之为反模式 [我认为 Eric 会说它在其他语言中也应该是反模式]。所以你会看到 `foreach` 陷入了“停止使用 `for` 循环;开始链接”的冷静与“C# [LINQ] 中故意没有 foreach 链接”的交火 (2认同)

Fly*_*wat 515

foreach是遍历实现集合IEnumerable.它通过调用GetEnumerator集合来执行此操作,该集合将返回一个Enumerator.

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

  • 的MoveNext()
  • 当前

Current返回Enumerator当前所在的对象,MoveNext更新Current下一个对象.

索引的概念对于枚举的概念是陌生的,并且不能完成.

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

与使用局部变量跟踪索引相比,我更倾向于在这种情况下使用for循环.

  • "显然,索引的概念对于枚举的概念来说是陌生的,而且无法完成." - 这是胡说八道,正如David B和bcahill的答案所表明的那样.索引是一个范围内的枚举,并且没有理由不能并行枚举两个东西......这正是Enumerable.Select的索引形式所做的. (138认同)
  • @JimBalter:这是我读过的第一个链接.我不知道它如何支持你的立场.在回复时,我也不会向人们投掷广告和分词.我举一个例子,你使用"胡说八道","非常混乱","完全错误和困惑","我得到了37个赞成"等等(我认为你的自我有点在这里.)相反,我我想礼貌地要求你不要用你的'argumentum ad verecundiam'来威胁别人,我想鼓励你想办法在StackOverflow上支持这里的人.我会说Jon Skeet在这方面是模范公民. (20认同)
  • 基本代码示例:`for(var i = 0; i <myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}` (11认同)
  • Pretzel:你使用Jon Skeet作为模范公民是错误的,因为他会像我所经历的那样对那些不同意他的人进行威胁(并且投票).Jim Balter:你的一些评论过于激进,你可以用较少的愤怒和更多的教育来表达你的观点. (9认同)
  • @Pretzel我引用的陈述(显然)不正确,我解释了原因.链接列表"没有索引"这一事实完全无关紧要,并显示出非同寻常的混乱. (5认同)
  • @Pretzel Jim的观点是,只要您可以将元素映射到整数序列,就可以对其进行索引。该类本身不存储索引是不重要的。此外,链表*确实*有顺序仅增强了Jim的位置。您需要做的就是按顺序编号每个元素。具体来说,您可以通过在迭代时增加计数来实现此目的,也可以生成具有相同长度的整数列表,然后将其压缩(如Python的[`zip`](https://docs.python。 org / 3 / library / functions.html#zip)函数)。 (5认同)
  • 底线是“链表是没有索引的 IEnumerable 的一个例子”是一个完全不相关的稻草人。没有人声称所有 IEnumerables 都“有一个索引”。 (3认同)
  • 这是可悲的非答案。您能想象这是否发布到Python或Rust问题上?人们会在发布指向“枚举”功能的指针时嘲笑它。 (3认同)
  • 从字面上可枚举意味着“可以编号”。API或基础数据结构没有提供随机访问的手段,这是完全不同的另一件事。 (3认同)
  • @JimBalter:好吧,我不会和你争论。也许微软应该将其称为 I-Iterable 而不是 IEnumerable(我承认,它确实暗示了一个数字。)但实现就是这样。没有索引。(我猜是出于线程安全的原因......) (2认同)
  • @Pretzel最好不要争论是要理解你是多么的错误和困惑......从阅读和理解OP的问题开始,以及你回答的评论,得到37个赞成票,以及我引用的两个答案.同样,链表"没有索引"这一事实完全无关紧要,并且在概念上深受困惑. (2认同)
  • 我们可以用@bcahill的答案替换掉这个“答案”吗,因为他回答了这个问题,并且比现在有更多的赞成票(489 vs 520)? (2认同)

use*_*562 121

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

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}
Run Code Online (Sandbox Code Playgroud)

需要一点扩展方法:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 
Run Code Online (Sandbox Code Playgroud)

  • 这个答案被低估了,让元组更干净 (6认同)
  • 修改为处理空集合:`public static IEnumerable &lt;(T item,int index)&gt; WithIndex &lt;T&gt;(此IEnumerable &lt;T&gt; self)=&gt; self?.Select(((item,index)=&gt;(item,index)) )?? 新List &lt;(T,int)&gt;();` (6认同)
  • 调用“Enumerated”方法对于习惯[其他语言](https://developer.apple.com/documentation/swift/array/1687832-enumerated)的人来说更容易识别可能很有用(也许可以交换顺序也是元组参数的一部分)。无论如何,“WithIndex”并不明显。 (5认同)
  • @2Toad 对于 null 条件,我认为您也可以使用 `Enumerable.Empty&lt;(T, int)&gt;()` 比创建空列表更有效。 (2认同)

Bra*_*son 111

可以做这样的事情:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这并不"真正"解决问题.这个想法很好,但它没有避免额外的计数变量 (10认同)

mik*_*son 91

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

foreach是一个有用的构造,并不是for在所有情况下都可以通过循环替换.

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

(当然,总是关闭读者是好的做法,但是如果你不这样做,编译器就不会抓住它 - 你不能保证你已经关闭了所有的读者,但是你可以更有可能通过获取而不会泄漏连接习惯使用foreach.)

可能存在该Dispose方法的隐式调用有用的其他示例.

  • 感谢您指出了这一点.相当微妙.您可以在http://www.pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator/和http://msdn.microsoft.com/en-us/library/aa664754获取更多信息. (VS.71)的.aspx. (2认同)

Amy*_*y B 59

字面答案 - 警告,性能可能不如仅使用int跟踪索引那么好.至少它比使用更好IndexOf.

您只需要使用Select的索引重载来使用知道索引的匿名对象包装集合中的每个项目.这可以针对实现IEnumerable的任何事情来完成.

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 当然,除了使用OfType而不是Cast的其他原因 - 这是我从不使用Cast. (12认同)
  • 使用OfType <T>()而不是Cast <T>()的唯一原因是枚举中的某些项可能会失败显式强制转换.对于对象,情况永远不会如此. (3认同)

Gez*_*zim 32

使用@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
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 没有必要比较listEnumerator.MoveNext()== true.这就像问电脑是否真的==真?:)只要说一下listEnumerator.MoveNext(){} (10认同)
  • @Zesty,你是完全正确的.我觉得在这种情况下添加它更具可读性,特别是对于那些不习惯输入除了<blahSize作为条件之外的任何东西的人. (8认同)
  • 您应该处理枚举器。 (2认同)

Pav*_*vel 30

使用LINQ,C#7和System.ValueTupleNuGet包,您可以这样做:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}
Run Code Online (Sandbox Code Playgroud)

您可以使用常规foreach构造,并且能够直接访问值和索引,而不是作为对象的成员,并且仅将两个字段保留在循环的范围内.出于这些原因,我相信如果您能够使用C#7和C#7,这是最好的解决方案System.ValueTuple.

  • 这是不同的,因为 .Select 是从 LINQ 内置的。不用自己写函数吧?不过,您需要让 VS 安装“System.ValueTuple”。 (2认同)

Bri*_*eon 25

您可以将原始枚举器包含在包含索引信息的另一个枚举器中.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}
Run Code Online (Sandbox Code Playgroud)

这是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;
        }            
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Lucas:不,但它会返回当前foreach迭代的索引.这是个问题. (7认同)

Dav*_*ock 25

使用计数器变量没有任何问题.实际上,无论你使用for,foreach while还是do一个计数器变量都必须在某处声明并递增.

如果你不确定你是否有一个适当索引的集合,请使用这个习惯用法:

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

如果你知道你的可索引集合是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'
}
Run Code Online (Sandbox Code Playgroud)

永远不需要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));
Run Code Online (Sandbox Code Playgroud)

我们AsParallel()在上面使用,因为它已经是2014年了,我们希望充分利用这些多核来加快速度.此外,对于"顺序"LINQ,您只能获得一个ForEach()扩展方法List<T>Array ...并且不清楚使用它是否比执行简单更好foreach,因为您仍然在运行单线程以获得更粗糙的语法.


mat*_*at3 18

这是我刚刚提出的解决这个问题的解决方案

原始代码:

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

更新的代码

enumerable.ForEach((item, index) => blah(item, index));
Run Code Online (Sandbox Code Playgroud)

扩展方法:

    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;
    }
Run Code Online (Sandbox Code Playgroud)


con*_*rio 13

只需添加自己的索引.把事情简单化.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么它更好? (2认同)

Pau*_*ell 12

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}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Par*_*rsa 10

为什么要foreach?!

如果您使用List,最简单的方法是使用for而不是foreach .

for (int i = 0 ; i < myList.Count ; i++)
{
    // Do something...
}
Run Code Online (Sandbox Code Playgroud)

或者如果你想使用foreach:

foreach (string m in myList)
{
     // Do something...
}
Run Code Online (Sandbox Code Playgroud)

你可以用它来表示每个循环的khow索引:

myList.indexOf(m)
Run Code Online (Sandbox Code Playgroud)

  • indexOf解决方案对于具有重复项的列表无效,并且也非常慢. (5认同)
  • 要避免的问题是您多次遍历 IEnumerable 的地方,例如获取项目的数量,然后是每个项目的数量。例如,当 IEnumerable 是数据库查询的结果时,这会产生影响。 (2认同)
  • myList.IndexOf() 是 O(n),所以你的循环将是 O(n^2)。 (2认同)

cru*_*ble 9

它只适用于List而不是任何IEnumerable,但在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();
Run Code Online (Sandbox Code Playgroud)

@Jonathan我没有说这是一个很好的答案,我只是说它只是表明它可以做他所要求的:)

@Graphain我不希望它快速 - 我不完全确定它是如何工作的,它可以在每次重复整个列表中找到一个匹配的对象,这将是比较的确认.

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

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

尽管如此,更简单,更具适应性,最好只计算你在foreach中所处的位置.

  • 在沉重的低调时不确定.当然表现令人望而却步但你确实回答了这个问题! (4认同)
  • 另一个问题是它只有在列表中的项目是唯一的情况下才有效. (2认同)

小智 9

// using foreach loop how to get index number:
    
foreach (var result in results.Select((value, index) => new { index, value }))
{
    // do something
}
Run Code Online (Sandbox Code Playgroud)

  • 虽然此代码可以回答问题,但提供有关“如何”和/或“为什么”解决问题的附加上下文将提高​​答案的长期价值。 (6认同)
  • 这只是[现有答案](/sf/answers/800629371/)的重复。 (3认同)

小智 8

int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}
Run Code Online (Sandbox Code Playgroud)

这适用于集合支持IList.

  • 两个问题:1)这是"O(n ^ 2)",因为在大多数实现中,"IndexOf"是"O(n)".2)如果列表中有重复项,则会失败. (67认同)
  • 天啊,我希望你没用过!:(它会使用你不想创建的变量 - 实际上,它会创建n + 1整数,因为该函数也必须创建一个才能返回 - 并且该索引的搜索速度远远低于每一步都有一个整数增量操作.为什么人们不会把这个答案投下来? (19认同)
  • 注意:O(n ^ 2)意味着对于大型集合来说这可能是灾难性的慢. (15认同)
  • 不要使用这个答案,我发现其中一条评论中提到的硬道理."如果列表中有重复的项目,则会失败."!!! (13认同)

Ian*_*nry 8

我就是这样做的,它的简洁/简洁很好,但是如果你在循环体中做了很多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);
    ...
}
Run Code Online (Sandbox Code Playgroud)


Tod*_*odd 7

主要答案指出:

“显然,索引的概念与枚举的概念是陌生的,无法做到。”

尽管当前的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);
}
Run Code Online (Sandbox Code Playgroud)

如果foreach传递了IEnumerable且无法解析IIndexedEnumerable,但是使用var index进行询问,则C#编译器可以使用IndexedEnumerable对象包装源,该对象添加了用于跟踪索引的代码。

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}
Run Code Online (Sandbox Code Playgroud)

为什么:

  • Foreach看起来更好,并且在业务应用程序中很少出现性能瓶颈
  • Foreach在内存上可以更有效。具有功能管道,而不是在每个步骤都转换为新集合。谁在乎它是否使用更多的CPU周期,更少的CPU缓存故障和更少的GC。
  • 要求编码器添加索引跟踪代码,破坏美观
  • 它非常容易实现(感谢MS)并且向后兼容

尽管此处的大多数人不是MS,但这是正确的答案,您可以游说MS来添加此类功能。您已经可以使用扩展功能构建自己的迭代器并使用元组,但是MS可以撒上语法糖以避免扩展功能


小智 6

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

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


ssa*_*eed 5

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

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}
Run Code Online (Sandbox Code Playgroud)

  • 现在算法是O(n ^ 2)(如果不是更糟).在使用它之前我会认真考虑*.它也是@crucible答案的副本 (13认同)
  • 小心这一点!如果您的列表中有重复的项目,它将获得第一个项目的位置! (2认同)

小智 5

您可以像这样编写循环:

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

添加以下结构和扩展方法后。

结构体和扩展方法封装了 Enumerable.Select 功能。

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);
    }
}
Run Code Online (Sandbox Code Playgroud)