首页 文章

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

提问于
浏览
720

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

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

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

30 回答

  • 2

    我不确定你是根据问题对索引信息做了什么 . 但是,在C#中,您通常可以调整IEnumerable.Select方法以获取您想要的索引 . 例如,我可能会使用类似的东西来判断值是奇数还是偶数 .

    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)的字典 .

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

    为了兴趣,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; }
    }
    

    但是,如果您在非Razor环境中执行单个操作(即可以作为lambda提供的操作),这将是正常的,但它不会成为非Razor上下文中for / foreach语法的可靠替代品 . .

  • 456

    我就是这样做的,它的简洁/简洁很好,但是如果你在循环体_407610中做了很多,那么它会变得很快 .

    foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
        string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
        ...
    }
    
  • 23

    你可以像这样写你的循环:

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

    添加以下struct和extension方法之后 .

    struct和extension方法封装了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);
        }
    }
    
  • 16

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

    int i = 0;
    foreach (var item in Collection)
    {
        item.index = i;
        ++i;
    }
    
  • 5

    我刚刚遇到这个问题,但在我的案例中考虑问题给出了最好的解决方案,与预期的解决方案无关 .

    这可能是一个很常见的情况,基本上,我正在从一个源列表中读取并在目标列表中基于它们创建对象,但是,我必须首先检查源项是否有效并且想要返回任何行错误 . 乍一看,我想将索引放入Current属性对象的枚举器中,但是,当我复制这些元素时,我隐含地知道当前目标的当前索引 . 显然它取决于你的目标对象,但对我来说它是一个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 list
        throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
      }
      else
      {
        destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
      }
    }
    

    我认为并不总是适用,但往往足以值得一提 .

    无论如何,关键是有时候你的逻辑中已经存在一个非显而易见的解决方案......

  • 2

    字面答案 - 警告,性能可能不如仅使用 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);
    }
    
  • 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();
    }
    

    这是 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;
            }            
        }
    }
    
  • 2

    使用计数器变量没有错 . 实际上,无论您使用 forforeach while 还是 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'
    }
    

    永远不需要'manually'通过调用 MoveNext() 来操作 IEnumerator 并查询 Current - foreach 正在为您节省特别麻烦...如果您需要跳过项目,只需在循环体中使用 continue 即可 .

    而且为了完整性,取决于你在索引中做了什么(上面的结构提供了很大的灵活性),你可以使用Parallel 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() ,因为它's 2014 already, and we want to make good use of those multiple cores to speed things up. Further, for '顺序'LINQ,you only get a ForEach() extension method on List<T> and Array ...并且不清楚使用它比做一个简单的 foreach 更好,因为你仍然在运行单线程以获得更好的语法 .

  • 79

    我在LINQPad Build 了这个:

    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);
    
  • 57

    Why foreach ?!

    最简单的方法是使用 for 而不是foreach if you are using List .

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

    或者如果你想使用foreach:

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

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

    myList.indexOf(m)
    
  • 31

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

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

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

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

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

  • 19

    我不认为这应该是非常有效的,但它的工作原理:

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

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

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

    • MoveNext()

    • 当前

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

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

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

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

  • 10

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

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

  • 8

    使用@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,因此当我们用尽元素进行迭代时,使循环条件使循环停止 .

  • 5

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

    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));
    
  • 4

    使用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 ,这是最佳解决方案 .

  • 2

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

    Original code:

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

    Updated code

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

    Extension Method:

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

    它只适用于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();
    

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

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

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

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

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

  • 3

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

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

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

    Use it like

    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));
    
  • 11

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

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

    我不相信有一种方法可以获得foreach循环的当前迭代的值 . 算上自己,似乎是最好的方式 .

    请问,为什么你想知道?

    看起来你最喜欢做三件事之一:

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

    2)计算对象以供以后后处理...集合具有您可以使用的Count属性 .

    3)根据循环中的顺序在对象上设置属性...虽然您可以在将对象添加到集合时轻松设置该属性 .

  • 4

    这样的事怎么样?请注意,如果myEnumerable为空,myDelimitedString可能为null .

    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;
    }
    
  • 3
    int index;
    foreach (Object o in collection)
    {
        index = collection.indexOf(o);
    }
    

    这适用于支持 IList 的集合 .

  • 63

    主要答案是:

    “显然,指数的概念对于枚举的概念来说是陌生的,而且无法完成 . ”

    虽然目前的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<T> : IEnumerable<T>
    {
        //Not index, because sometimes source IEnumerables are transient
        public long IterationNumber { get; }
    }
    

    为什么:

    • Foreach看起来更好,在业务应用程序中很少是性能瓶颈

    • Foreach可以更有效地记忆 . 拥有一系列功能,而不是在每一步都转换为新的集合 . 如果CPU缓存故障较少且GC.Collects较少,谁会关心它是否会使用更多的CPU周期

    • 要求编码人员添加索引跟踪代码,破坏美感

    • 它很容易实现(感谢MS)并且向后兼容

    虽然这里的大多数人都不是MS,但这是一个正确的答案,你可以游说MS添加这样的功能 . 您可以使用extension function and use tuples构建自己的迭代器,但MS可以使用语法糖来避免扩展函数

  • 3

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

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

    Ian Mercer在Phil Haack's blog上发布了类似的解决方案:

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

    这将通过使用this overload of Linq's Select获取项目( item.value )及其索引( item.i ):

    函数[select Select]中的第二个参数表示源元素的索引 .

    new { i, value } 正在创建一个新的anonymous object .

相关问题