Як отримати індекс поточної ітерації циклу foreach?


938

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

Наприклад, я зараз роблю щось подібне залежно від обставин:

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

1
Вибір кастингу для foreach, як правило, не оптимізований для мене, ніж просто використання доступу до індексу для колекції, хоча у багатьох випадках це буде рівним. Мета foreach - зробити ваш код читабельним, але він (як правило) додає шар непрямості, який не є безкоштовним.
Брайан

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

2
Привіт Брайан Гедеон - безумовно, згоден (це було кілька років тому, і я був набагато менш досвідченим у той час). Однак, хоча Dictionaryвона не піддається індексації, ітерація Dictionaryперетинає її в певному порядку (тобто, Перелік обчислюється тим, що він дає елементи послідовно). У цьому сенсі можна сказати, що ми шукаємо не індекс всередині колекції, а індекс поточного перерахованого елемента в межах перерахунку (тобто, чи є ми на першому чи п'ятому чи останньому перерахованому елементі).
Метт Мітчелл

4
foreach також дозволяє компілятору пропускати межі, перевіряючи кожен доступ до масиву у складеному коді. Використання для індексу змусить час виконання перевірити, чи безпечний ваш доступ до індексу.
IvoTops

1
Але це помилково. Якщо ви не змінюєте змінну ітерації циклу for в циклі, компілятор знає, які є його межі, і не потрібно перевіряти їх ще раз. Це настільки поширений випадок, що будь-який гідний компілятор буде його реалізовувати.
Джим Балтер

Відповіді:


550

Це foreachдля ітерації над колекціями, які реалізуються IEnumerable. Це робиться, зателефонувавши GetEnumeratorдо колекції, яка поверне анонім Enumerator.

Цей перелік має метод та властивість:

  • MoveNext ()
  • Поточний

Currentповертає об'єкт, на якому перебуває "Перелік", MoveNextоновлення Currentна наступний об'єкт.

Поняття індексу є чужим поняттю перерахування, і його неможливо зробити.

Через це більшість колекцій можна пройти за допомогою індексатора та конструкції for циклу.

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


165
"Очевидно, що поняття індексу чуже поняттю перерахування, і його неможливо зробити". - Це нісенітниця, оскільки зрозумілі відповіді Девіда Б і bcahill. Індекс - це перерахування в діапазоні, і немає причини, що не можна перераховувати дві речі паралельно ... саме це і робить форма індексації Enumerable.Select.
Джим Балтер

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

22
@JimBalter: Це перше посилання, яке я прочитав. Я не бачу, як це підтримує вашу позицію. Я також не заважаю людям рекламних домів і розбіжних слів на людей, коли відповідають. Я наводжу в якості прикладу ваше використання "нісенітниць", "надзвичайної плутанини", "абсолютно неправильних і заплутаних", "я отримав 37 підсумків" і т. Д. (Я думаю, ваше его тут трохи на межі.) Натомість я хотів би ввічливо попросити, щоб ви не переслідували інших своїми аргументами «argumentum ad verecundiam», і я хотів би замість цього заохотити вас думати про способи підтримки людей тут у StackOverflow. Я б сказав, що Джон Скіт є зразковим громадянином у цьому плані.
Крендель

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

7
Точка Джерела Джима полягає в тому, що доки ви можете зіставити елементи в послідовності цілих чисел, ви можете їх індексувати. Те, що сам клас не зберігає індекс, знаходиться поруч з точкою. Крім того, що пов'язаний список дійсно є замовлення тільки підсилює позицію Джима. Все, що вам потрібно зробити, це пронумерувати кожен елемент по порядку. Зокрема, ви можете досягти цього, збільшуючи кількість під час ітерації, або ви можете створити список цілих чисел з однаковою довжиною, а потім скопіювати їх (як у zipфункції Python ).
jpmc26

666

Ян Мерсер опублікував подібне рішення, як це у своєму блозі Філа Хака :

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

Це отримує вам елемент ( item.value) та його індекс ( item.i), використовуючи це перевантаження LINQSelect :

другий параметр функції [всередині 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 анонсів на момент написання)? Наскільки я бачу, це важче зрозуміти, ніж просто наростити лічильник, отже, менш рентабельний, він використовує більше пам’яті і, ймовірно, повільніше. Я щось пропускаю?
Rich N

182

Нарешті, 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)); 

8
Ця відповідь недооцінена, оскільки кортежі значно чистіші
Тодд,

7
Змінено для обробки нульових колекцій: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не все одно очевидно.
ФернАндр

114

Можна зробити щось подібне:

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

Це не спрацьовує, якщо у нас є цикл повернення в циклі для циклу, якщо ви для цього зміните "ForEachWithIndex", це не є загальним, краще писати звичайний цикл
Shankar Raju

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

95

Я не згоден з коментарями, що forцикл - кращий вибір у більшості випадків.

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

Наприклад, якщо у вас є DataReader і проходить цикл через усі записи, використовуючи a, 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> () - це те, якщо деякі елементи перерахунку можуть не мати явного виступу. Для об'єкта це ніколи не буде.
дальбик

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.


Чим це відрізняється від відповіді користувача1414213562 ?
Едвард Брей

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


10
Не потрібно порівнювати listEnumerator.MoveNext () == true. Це як запитати комп’ютер, чи правда == вірно? :) Просто скажіть, якщо listEnumerator.MoveNext () {}
Будь ласка, натисніть тут

9
@Zesty, ти абсолютно прав. Я відчув, що в цій справі доцільніше її додати, особливо для людей, які не звикли вводити як умову нічого, крім i <blahSize.
Гезим

@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це рятує вас від конкретного клопоту ... якщо вам потрібно пропустити елементи, просто використовуйте 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

Він буде працювати лише для Списку, а не для будь-якого 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 Я б не очікував, що це буде швидко - я не зовсім впевнений, як це працює, він міг би повторювати весь список кожного разу, щоб знайти відповідний об'єкт, який був би пекелотом порівнянь.

При цьому, Список може зберігати індекс кожного об'єкта разом з підрахунком.

Джонатан, здається, має кращу ідею, якби він детальніше розробив?

Було б краще просто порахувати, куди ви задумали, хоча, простіше і пристосуваніше.


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

Навіщо пропонувати ?!

Найпростіший спосіб використання для замість Еогеасп , якщо ви використовуєте список :

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

Або якщо ви хочете використовувати foreach:

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

Ви можете скористатися цим, щоб знати індекс кожного циклу:

myList.indexOf(m)

8
Рішення indexOf недійсне для списку з дублікатами, а також дуже повільне.
tymtam

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) означає, що це може бути катастрофічно повільним для великої колекції.
O'Rooney

Чудовий винахід використовувати метод IndexOf! Це те, що я шукав, щоб отримати індекс (число) в циклі foreach! Big Thx
Mitja Bonca

19
Боже, сподіваюся, ти цим не користувався! :( НЕ використовує ту змінну, яку ви не хотіли створювати - насправді вона створить n + 1 ints, оскільки ця функція повинна створити і її, щоб повернутись, - і цей показник пошуку набагато, набагато повільніше, ніж одна операція з збільшенням цілих кроків на кожному кроці. Чому люди не проголосують за цю відповідь?
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
Будьте обережні з цим! Якщо у вашому списку є дублюваний елемент, він отримає позицію першого!
Sonhja

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

Після додавання наступного методу структури та розширення.

Метод структури та розширення інкапсулює 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для пари (індекс, пункт).
CodesInChaos

3

Для інтересу Філ Хак просто написав приклад цього в контексті шаблону-делегата "Бритви" ( 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); '
Warren LaFrance

1
Хтось може мені пояснити, що це за річ O (n * n)?
Аксель

@Axel це в основному означає, що операції, необхідні для обчислення результату, збільшуються квадратично, тобто, якщо є 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) string 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.