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


939

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

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

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

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

8
Я бы сказал, что основная цель foreachсостоит в том, чтобы предоставить общий итерационный механизм для всех коллекций независимо от того, являются ли они indexable ( List) или нет ( Dictionary).
Брайан Гидеон

2
Привет Брайан Гидеон - определенно согласен (это было несколько лет назад, и я был гораздо менее опытным в то время). Однако, хотя Dictionaryон и не индексируется, итерация Dictionaryвыполняет его в определенном порядке (т. Е. Перечислитель индексируется благодаря тому, что он последовательно выдает элементы). В этом смысле мы могли бы сказать, что мы ищем не индекс в коллекции, а скорее индекс текущего перечисляемого элемента в перечислении (т.е. находимся ли мы в первом, пятом или последнем перечисляемом элементе).
Мэтт Митчелл

4
foreach также позволяет компилятору пропускать границы, проверяя каждый доступ к массиву в скомпилированном коде. Использование for с индексом заставит среду выполнения проверить, безопасен ли ваш доступ к индексу.
IvoTops,

1
Но это неверно. Если вы не измените переменную итерации цикла for в цикле, компилятор знает, каковы его границы, и ему не нужно проверять их снова. Это настолько распространенный случай, что любой приличный компилятор будет его реализовывать.
Джим Балтер

Ответы:


552

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

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

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

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

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

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

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


165
«Очевидно, что понятие индекса чуждо понятию перечисления и не может быть сделано». - Это чепуха, как ясно дают ответы Дэвид Б. и Бахилл. Индекс - это перечисление в диапазоне, и нет причины, по которой нельзя перечислять две вещи параллельно ... это именно то, что делает форма индексации Enumerable.Select.
Джим Балтер

11
Пример основного кода:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Чед Хедгкок

22
@JimBalter: это первая ссылка, которую я прочитал. Я не понимаю, как это поддерживает вашу позицию. Я также не швыряю в людей ad-homs и нерешительные слова при ответе. Я привожу в качестве примера ваше использование «ерунды», «необычайной путаницы», «совершенно неправильного и растерянного», «я получил 37 голосов» и т. Д. (Я думаю, что ваше эго находится здесь на грани.) Вместо этого я Я хотел бы вежливо попросить вас не запугивать других своим «аргументом ad verecundiam», и я бы вместо этого призвал вас подумать о способах поддержки людей здесь, в StackOverflow. Я бы сказал, что Джон Скит является образцовым гражданином здесь в этом отношении.
Крендель

11
Крендель: Вы используете Джона Скита в качестве образцового гражданина ошибочно, так как он запугивает (и понижает голос) человека, который не согласен с ним, как я понял. Джим Балтер: Некоторые из ваших комментариев были чрезмерно агрессивными, и вы могли бы представить свои очки с меньшим гневом и большим образованием.
Suncat2000

7
@Pretzel Джим считает, что пока вы можете отображать элементы в последовательности целых чисел, вы можете индексировать их. То, что сам класс не хранит индекс, не относится к делу. Кроме того, что связанный список действительно есть заказ только усиливает позицию Джима. Все, что вам нужно сделать, это пронумеровать каждый элемент по порядку. В частности, вы можете достичь этого, увеличив счет во время итерации, или вы можете сгенерировать список целых чисел с одинаковой длиной, а затем сжать их (как в zipфункции Python ).
jpmc26

666

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

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

Это получает вас item ( item.value) и его index ( item.i), используя перегрузку LINQSelect :

второй параметр функции [inside 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;
}

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

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

9
Это решение подходит для случая шаблона Razor, где аккуратность шаблона представляет собой нетривиальную задачу проектирования, и вы также хотите использовать индекс каждого перечисляемого элемента. Однако имейте в виду, что выделение объекта из «обтекания» увеличивает затраты (в пространстве и времени) поверх (неизбежного) приращения целого числа.
Дэвид Баллок

6
@mjsr Документация здесь .
Торкил Холм-Якобсен

19
Извините - это умно, но действительно ли это более читабельно, чем создание индекса вне foreach и увеличение его в каждом цикле?
jbyrd

13
В более поздних версиях C # вы также используете кортежи, поэтому у вас будет что-то вроде этого: foreach (var (item, i) в Model.Select ((v, i) => (v, i))) Позволяет вам получить доступ к элементу и индексу (i) непосредственно внутри цикла for с помощью деконструкции кортежа.
Хаукман

5
Может кто-нибудь объяснить мне, почему это хороший ответ (более 450 голосов на момент написания статьи)? Насколько я понимаю, это сложнее понять, чем просто увеличивать счетчик, следовательно, его легче обслуживать, он использует больше памяти и, вероятно, медленнее. Я что-то пропустил?
Богатый N

183

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

9
Этот ответ недооценен, имея кортежи намного чище
Тодд

8
Модифицировано для обработки нулевых коллекций:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Toad

Хороший. Мне действительно нравится это решение больше всего.
FranzHuber23

Это лучший ответ
w0ns88

2
Возможно, было бы полезно вызвать метод, Enumeratedчтобы он был более узнаваемым для людей, привыкших к другим языкам (и, возможно, поменяйте местами порядок параметров кортежа). WithIndexВо всяком случае , это не очевидно.
FernAndr

115

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

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

12
Это не «действительно» решает проблему. Идея хороша, но она не избегает дополнительной переменной подсчета
Atmocreations

Это не работает, если у нас есть оператор return в нашем цикле for, если вы измените для этого «ForEachWithIndex», тогда это не является универсальным, лучше написать регулярный цикл for
Shankar Raju

Ваш вызов ForEachWithIndex эквивалентен этому вызову с использованием Linq Select, который принимает строку и индекс:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861

95

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

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

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

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

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


2
Спасибо за указание на это. Довольно тонкий. Вы можете получить больше информации на pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator и msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx .
Марк Мейер

+1. Я писал более подробно о том foreach, чем отличается for(и ближе к while) на Programmers.SE .
Арсений Мурзенко

64

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

3
Единственная причина использовать OfType <T> () вместо Cast <T> (), если некоторые элементы в перечислении могут не выполнить явное приведение. Для объекта это никогда не будет иметь место.
dahlbyk

13
Конечно, за исключением другой причины использовать OfType вместо Cast - это то, что я никогда не использую Cast.
Эми Б

36

Используя 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.



@ EdwardBrey Полагаю, это не так. На этот вопрос есть много ответов, я, вероятно, просто пропустил его или не заметил, что он делал, потому что он разделил часть логики на метод расширения.
Павел

1
Это отличается, потому что .Select является встроенным из LINQ. Вам не нужно писать свою собственную функцию? Вам нужно будет установить VS "System.ValueTuple".
Антон

33

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


10
Нет необходимости сравнивать listEnumerator.MoveNext () == true. Это все равно что спрашивать компьютер, если правда == правда? :) Просто скажите, если listEnumerator.MoveNext () {}
Zesty

9
@ Зести, ты абсолютно прав. Я чувствовал, что в этом случае его легче читать, особенно для людей, которые не привыкли вводить что-либо, кроме меня, как условие.
Гезим

@Gezim Я не против предикатов здесь, но я понимаю, что это не похоже на предикат.
Джон Дворак

1
Вы должны утилизировать счетчик.
Антонин Лейсек

@ AntonínLejsek Перечислитель не реализует IDisposable.
Эдвард Брей

27

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


26

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

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

Это на самом деле не возвращает индекс элемента. Вместо этого он вернет индекс в перечисляемом списке, который может быть только подсписком списка, предоставляя тем самым точные данные только тогда, когда подсписок и список имеют одинаковый размер. По сути, всякий раз, когда в коллекции есть объекты, не относящиеся к запрошенному типу, ваш индекс будет неверным.
Лукас Б

7
@Lucas: Нет, но он вернет индекс текущей итерации foreach. Это был вопрос.
Брайан Гидеон

20

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

Оригинальный код:

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

16

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

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

15

Это будет работать только для 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();

@ Джонатан Я не сказал, что это был отличный ответ, я просто сказал, что он просто показывает, что можно сделать то, что он просил :)

@Graphain Я бы не ожидал, что он будет быстрым - я не совсем уверен, как это работает, он может повторять весь список каждый раз, чтобы найти соответствующий объект, который был бы адским сравнением.

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

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

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


5
Не уверен в тяжелом понижении. Конечно, производительность делает это непомерно высоким, но вы ответили на вопрос!
Мэтт Митчелл

4
Другая проблема заключается в том, что это работает, только если элементы в списке являются уникальными.
CodesInChaos,

14

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

Да, и MS должен расширить CLR / BCL, чтобы сделать эту вещь родной.
Тодд

14

Почему foreach ?!

Самый простой способ использования для вместо Еогеаспа , если вы используете список :

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

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

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

Вы можете использовать это, чтобы узнать индекс каждого цикла:

myList.indexOf(m)

8
Решение indexOf недопустимо для списка с дубликатами и также очень медленно.
тымтам

2
Проблема, которую следует избегать, - это когда вы пересекаете IEnumerable несколько раз, например, чтобы получить количество элементов, а затем каждый элемент. Это имеет значение, когда IEnumerable является результатом запроса к базе данных, например.
Дэвид Кларк

2
myList.IndexOf () - это O (n), поэтому ваш цикл будет O (n ^ 2).
Патрик Борода

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

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


68
Две проблемы: 1) Так O(n^2)как в большинстве реализаций IndexOfесть O(n). 2) Сбой, если в списке есть повторяющиеся элементы.
CodesInChaos,

15
Примечание: O (n ^ 2) означает, что это может быть катастрофически медленным для большой коллекции.
О'Руни

Отличное изобретение для использования метода IndexOf! Это то, что я искал, чтобы получить индекс (число) в цикле foreach! Большое спасибо
Митя Бонка

19
Боже, я надеюсь, ты не использовал это! :( Она использует ту переменную, которую вы не хотели создавать - на самом деле она создаст n + 1 целых, потому что эта функция должна создать ее также для возврата - и этот индекс поиска намного, намного медленнее, чем одно целочисленное увеличение на каждом шаге. Почему люди не проголосуют за этот ответ?
canahari

13
Не используйте этот ответ, я нашел жесткую правду, упомянутую в одном из комментариев. «Это невозможно, если в списке есть повторяющиеся элементы.» !!!
Брюс

9

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

8

Ответ: лоббируйте языковую группу C # для прямой языковой поддержки.

Ведущий ответ гласит:

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

Хотя это верно для текущей версии языка C # (2020), это не концептуальное ограничение CLR / Language, это можно сделать.

Команда разработчиков языка Microsoft C # может создать новую функцию языка C #, добавив поддержку нового интерфейса 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 ()используется и with var indexприсутствует, то компилятор ожидает, что коллекция элементов объявит IIndexedEnumerableинтерфейс. Если интерфейс отсутствует, компилятор может полифиллом обернуть исходный код объектом IndexedEnumerable, который добавляет код для отслеживания индекса.

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

Позже, CLR может быть обновлен, чтобы иметь внутреннее отслеживание индекса, которое используется, только если withуказано ключевое слово, а источник не реализует напрямуюIIndexedEnumerable

Почему:

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

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


Подождите, значит, эта языковая функция уже существует или она предлагается на будущее?
Павел

1
@Pavel Я обновил ответ, чтобы быть ясным. Этот ответ был предоставлен для противодействия ведущему ответу, который гласит: «Очевидно, что понятие индекса чуждо понятию перечисления и не может быть сделано».
Тодд

6

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

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

13
И теперь алгоритм O (n ^ 2) (если не хуже). Я бы очень тщательно подумал, прежде чем использовать это. Это также дубликат ответа
@crucible

1
@BradleyDotNET совершенно прав, не используйте эту версию.
Оскар

1
Будьте осторожны с этим! Если у вас есть дублированный элемент в вашем списке, он получит позицию первого!
Соня

5

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

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

5

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

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

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

Метод 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);
    }
}

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

Я бы использовал structдля (index, item) пары.
CodesInChaos,

3

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


3

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

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

3

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

Вы также можете использовать string.join в c # Например: var joinResult = string.Join (",", listOfNames); '
Уоррен ЛаФранс

1
Может кто-нибудь объяснить мне, что это за штука?
Аксель

@ По сути это означает, что операции, необходимые для вычисления результата, увеличиваются квадратично, т. Е. Если есть nэлементы, то операции выполняются n * nили n-квадратируются. en.wikipedia.org/wiki/…
Ричард Ханселл

2

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

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

Похоже, вам больше всего нравится делать одну из трех вещей:

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

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

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


4) Случай, в который я попадал несколько раз, - это нечто иное, что нужно сделать при первом или последнем проходе - скажем, список объектов, которые вы собираетесь напечатать, и вам нужны запятые между элементами, но не после последнего элемента.
Лорен Печтел

2

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

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


2

Как насчет этого? Обратите внимание, что 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;
}

Несколько проблем здесь. А) дополнительная скобка. B) строка concat + = за итерацию цикла.
enorl76

2

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

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

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

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


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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.