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

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

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

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

Ответ 1

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

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

  • MoveNext()
  • Текущий

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

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

По этой причине большинство коллекций можно просматривать с помощью индексатора и конструкции цикла for.

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

Ответ 2

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

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 } создает новый анонимный объект.

Вы можете избежать выделения кучи с помощью ValueTuple если вы используете С# 7.0 или более ValueTuple:

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

Вы также можете устранить item. с помощью автоматической деструктуризации:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="[email protected]">@value</li>
}
</ol>

Ответ 3

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

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

Ответ 4

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

Ответ 5

Я не согласен с комментариями, что цикл for является лучшим выбором в большинстве случаев.

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

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

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

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

Ответ 6

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

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

Ответ 8

Используя LINQ, С# 7 и пакет System.ValueTuple NuGet, вы можете сделать это:

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

Вы можете использовать регулярную конструкцию foreach и иметь возможность напрямую обращаться к значению и индексу, а не как к объекту, и сохранять оба поля только в области цикла. По этим причинам я считаю, что это лучшее решение, если вы можете использовать С# 7 и System.ValueTuple.

Ответ 9

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

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

Ответ 10

Нет ничего плохого в использовании переменной счетчика. Фактически, если вы используете for, foreach while или do, переменная-счетчик должна быть объявлена ​​и увеличена.

Итак, используйте эту идиому, если не уверены, есть ли у вас коллекция с соответствующим индексом:

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

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

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

Ответ 11

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

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

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

Ответ 12

int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Это будет работать для коллекций, поддерживающих IList.

Ответ 13

Он будет работать только для списка, а не для 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 для сравнения.

Тем не менее, List может содержать индекс каждого объекта вместе со счетчиком.

Джонатан, кажется, имеет лучшую идею, если бы он уточнил?

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

Ответ 14

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

Ответ 15

Просто добавьте свой собственный индекс. Будь проще.

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

Ответ 16

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

Ответ 17

Почему 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)

Ответ 18

Лучше использовать безопасную конструкцию ключевого слова continue, подобную этой

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

Ответ 19

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

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

Ответ 20

В главном ответе говорится:

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

Хотя это верно для текущей версии С#, это не концептуальный предел.

Создание новой функции языка С# от MS могло бы решить эту проблему, наряду с поддержкой нового интерфейса IIndexedEnumerable

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

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

Если foreach передается IEnumerable и не может разрешить IIndexedEnumerable, но задается с индексом var, тогда компилятор С# может обернуть источник с помощью объекта IndexedEnumerable, который добавляет в код для отслеживания индекса.

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

Почему:

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

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

Ответ 21

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

Ответ 22

Для интереса, Фил Хаак только что написал пример этого в контексте Ramor Templated Delegate (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, если вы выполняете одну операцию (то есть, которая может быть предоставлена ​​как лямбда), это не будет твердой заменой синтаксиса for/foreach в non-Razor -Различные контексты.

Ответ 23

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

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

Ответ 24

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

Ответ 25

Вы можете написать свой цикл следующим образом:

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

Ответ 26

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

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

Ответ 27

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

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

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

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

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

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

Ответ 28

Как насчет чего-то подобного? Обратите внимание, что myDelimitedString может быть пустым, если 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;
}

Ответ 29

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

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

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

В любом случае, дело в том, что иногда есть неочевидное решение уже в вашей логике...

Ответ 30

Я не был уверен, что вы пытались сделать с информацией индекса, основанной на вопросе. Однако в С# вы обычно можете адаптировать метод 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) в списке.