Как получить индекс текущей итерации цикла foreach?

есть ли какая-то редкая языковая конструкция, с которой я не сталкивался (например, несколько, которые я узнал недавно, некоторые из переполнения стека) в C#, чтобы получить значение, представляющее текущую итерацию цикла foreach?

например, в настоящее время я делаю что-то подобное в зависимости от обстоятельств:

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

30 ответов


на foreach предназначен для итерации по коллекциям, реализующим IEnumerable. Он делает это, вызывая GetEnumerator о коллекции, которая вернет Enumerator.

этот перечислитель имеет метод и свойство:

  • MoveNext ()
  • настоящее

Current возвращает объект, на котором в данный момент находится перечислитель,MoveNext обновления Current на следующий объект.

очевидно, что понятие индекса чуждо понятию перечисления и не может быть сделано.

из-за этого большинство коллекций могут быть пройдены с помощью индексатора и конструкции цикла for.

Я очень предпочитаю использовать цикл for В этой ситуации по сравнению с отслеживанием индекса с локальной переменной.


Ян Мерсер опубликовал аналогичное решение, как это на блог Фила Хаака:

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

это дает вам пункт (item.value) и его индекс (item.i) С помощью эта перегрузка LINQ на это Select:

второй параметр функции [inside Select] представляет индекс исходного элемента.

на new { i, value } создает новый анонимный объект.


может сделать что-то вроде этого:

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 loop является лучшим выбором в большинстве случаев.

foreach - полезная конструкция, а не заменяемая на for цикл при любых обстоятельствах.

например, если у вас есть DataReader и цикл через все записи, используя foreach он автоматически называет Dispose метод и закрывает считыватель (который затем может автоматически закрыть соединение). Поэтому это безопаснее, так как предотвращает утечка соединения, даже если вы забыли закрыть считыватель.

(конечно, это хорошая практика, чтобы всегда закрывать читателей, но компилятор не собирается ловить его, если вы этого не сделаете - вы не можете гарантировать, что вы закрыли всех читателей, но вы можете сделать это более вероятно, что вы не будете утечки соединений, войдя в привычку использовать foreach.)

могут быть и другие примеры неявного вызова Dispose метод полезен.


буквальный ответ -- предупреждение, производительность может быть не так хороша, как просто с помощью 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);
}

наконец, 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)); 

используя ответ @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, если есть следующий элемент, и к нему можно получить доступ, что делает условие цикла остановкой цикла, когда у нас заканчиваются элементы для итерации.


вы можете обернуть исходный перечислитель другим, который содержит информацию об индексе.

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

вот решение, которое я только что придумал для этой проблемы

исходный код:

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

обновленный код

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 void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }

нет ничего плохого в использовании переменной счетчика. На самом деле, используете ли вы for, foreach 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'
}

никогда не должно быть необходимости "вручную" управлять IEnumerator ссылкой MoveNext() и опрос 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,вы получаете только ForEach() метод расширения на List<T> и Array ... и не ясно, что использовать его лучше, чем делать простой foreach, так как вы все еще используете однопоточный для более уродливого синтаксиса.


использование 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 index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

это будет работать для коллекций обслуживания IList.


он будет работать только для списка, а не для 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 я бы не ожидал, что это будет быстро - я не совсем уверен, как это работает, он может повторять через весь список каждый раз, чтобы найти соответствующий объект, который был бы helluvalot сравнивает.

то есть, список может держите индекс каждого объекта вместе с count.

у Джонатана, кажется, есть идея получше,если он уточнит?

было бы лучше просто подсчитать, где вы находитесь в foreach, хотя, проще и более адаптируемы.


вот как я это делаю, что приятно для его простоты/краткости, но если вы делаете много в теле цикла 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);
    ...
}

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

Почему foreach ?!

самый простой способ-использовать на вместо foreach если вы используете List .

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

или если вы хотите использовать foreach :

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

вы можете использовать это для индекса khow каждого цикла:

myList.indexOf(m)

лучше использовать ключевое слово continue безопасная конструкция такой

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

Я не думаю, что это должно быть довольно эффективно, но это работает:

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

Я построил это в помощью 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);

если коллекция является списком, вы можете использовать List.IndexOf, как в:

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

Я не верю, что есть способ, чтобы получить значение текущей итерации цикла foreach. Считать себя, кажется, лучший способ.

могу я спросить, почему вы хотите знать?

кажется, что вы, скорее всего, будете делать одну из трех вещей:

1) Получение объекта из коллекции, но в этом случае он у вас уже есть.

2) подсчет объектов для последующей обработки...коллекции имеют свойство Count, которое вы могли бы использовать.

3) Установка свойства объекта на основе его порядка в цикле...хотя вы можете легко установить это при добавлении объекта в коллекцию.


мое решение этой проблемы-это метод расширения 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));

Как насчет чего-то вроде этого? Обратите внимание, что myDelimitedString может быть null, если myEnumerable пуст.

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

для интереса Фил Хаак просто написал пример этого в контексте шаблонного делегата 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; }
}

однако, хотя это было бы хорошо в среде без бритвы, если вы делаете одна операция (т. е. та, которая может быть предоставлена как лямбда), это не будет твердой заменой синтаксиса for/foreach в контекстах без бритвы.


Я не был уверен, что вы пытаетесь сделать с индексной информацией, основанной на вопросе. Однако в C# вы обычно можете адаптировать IEnumerable.Выберите способ, чтобы получить индекс из того, что вы хотите. Например, я мог бы использовать что-то вроде этого для Является ли значение четное или нечетное.

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) в списке.


Вы можете написать свой цикл вроде этого:

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

после добавления следующей структуры и метода расширения.

структура и метод расширения инкапсулируют Enumerable.Выберите функциональность.

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#, это не концептуальный предел.

создание новой функции языка C# MS может решить эту проблему вместе с поддержкой нового интерфейса 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, затем компилятор C# может обернуть источник объектом IndexedEnumerable, который добавляет код для отслеживания индекса.

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

почему:

  • Foreach выглядит лучше, и в бизнес-приложениях редко является узким местом производительности
  • Foreach может быть более эффективным на памяти. Наличие конвейера функций вместо преобразования в новые коллекции на каждом шаге. Кого волнует, если он использует еще несколько циклов процессора, если есть меньше ошибок кэша процессора и меньше GC.Собирает
  • требование к кодеру добавить код отслеживания индекса, портит красоту
  • это довольно легко реализовать (спасибо MS) и обратно совместим

хотя большинство людей здесь не MS, это правильный ответ, и вы можете лоббировать MS, чтобы добавить такую функцию. Вы уже можете создать свой собственный итератор с помощью функция расширения и использование кортежей, но MS может посыпать синтаксический сахар, чтобы избежать функция расширения


Если ваша коллекция не может вернуть индекс объекта с помощью какого-либо метода, единственный способ-использовать счетчик, как в вашем примере.

однако при работе с индексами единственным разумным ответом на проблему является использование цикла for. Все остальное вводит сложность кода, не говоря уже о сложности времени и пространства.


У меня просто была эта проблема, но обдумывание проблемы в моем случае дало лучшее решение, не связанное с ожидаемым решением.

это может быть довольно распространенный случай, в основном, я читаю из одного исходного списка и создаю объекты на их основе в списке назначения, однако я должен проверить, являются ли исходные элементы действительными сначала и хочу вернуть строку любой ошибки. На первый взгляд, я хочу получить индекс в перечислитель объекта в текущем собственность, однако, поскольку я копирую эти элементы, я неявно знаю текущий индекс в любом случае из текущего назначения. Очевидно, это зависит от вашего целевого объекта, но для меня это был список, и, скорее всего, он будет реализовывать 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]});
  }
}

Не всегда применимо, но достаточно часто, чтобы быть стоит упомянуть, я думаю.

во всяком случае, дело в том, что иногда в логике у вас есть неочевидное решение...


Это не отвечает на ваш конкретный вопрос, но предоставляет вам решение вашей проблемы: используйте цикл for для запуска коллекции объектов. тогда у вас будет текущий индекс вы работаете.

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