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

701

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

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

int i=0;
foreach (Object o in collection)
{
    // ...
    i++;
}
  • 1
    Как правило, поиск при помощи приведения типа foreach не будет для меня более оптимизированным, чем просто использование доступа к коллекции на основе индекса, хотя во многих случаях он будет одинаковым. Цель foreach - сделать ваш код читабельным, но это (обычно) добавляет слой косвенности, который не является бесплатным.
  • 8
    Я бы сказал, что основная цель foreach - предоставить общий итерационный механизм для всех коллекций независимо от того, индексируются ли они ( List ) или нет ( Dictionary ).
Показать ещё 4 комментария
Теги:
foreach

33 ответа

439
Лучший ответ

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

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

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

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

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

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

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

  • 111
    «Очевидно, что понятие индекса чуждо понятию перечисления и не может быть сделано». - Это чепуха, как ясно дают ответы Дэвид Б. и Бахилл. Индекс - это перечисление в диапазоне, и нет никакой причины, по которой нельзя перечислять две вещи параллельно ... это именно то, что делает форма индексации Enumerable.Select.
  • 9
    Пример основного кода: for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Показать ещё 12 комментариев
464

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

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="item_@i">@value</li>
}
</ol>
  • 7
    Это решение подходит для случая шаблона Razor, где аккуратность шаблона представляет собой нетривиальную задачу проектирования, и вы также хотите использовать индекс каждого перечисляемого элемента. Однако имейте в виду, что выделение объекта из «обтекания» увеличивает затраты (в пространстве и времени) поверх (неизбежного) приращения целого числа.
  • 3
    просто удивительно, но где это задокументировано ... Я имею в виду два параметра в делегате Select, я никогда не видел этого ... и я не могу найти объяснение документации ..... экспериментируя с этим выражением, я увидел это значение ничего не значит и имеет значение только позиция, первый параметр элемент, второй параметр индекс.
Показать ещё 6 комментариев
108

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

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));
    }
}
  • 10
    Это не «действительно» решает проблему. Идея хороша, но она не избегает дополнительной переменной подсчета
  • 0
    Это не работает, если у нас есть оператор return в нашем цикле for, если вы измените ForEachWithIndex для этого, тогда он не является универсальным, лучше написать обычный цикл for
Показать ещё 1 комментарий
76

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

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

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

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

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

66

Наконец, С# 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)); 
  • 1
    Этот ответ недооценен, имея кортежи намного чище
  • 1
    Модифицировано для обработки нулевых коллекций: public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
59

Буквенный ответ - предупреждение, производительность может быть не такой хорошей, как просто использовать 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> (), если некоторые элементы в перечислении могут не выполнить явное приведение. Для объекта это никогда не будет иметь место.
  • 11
    Конечно, за исключением другой причины использовать OfType вместо Cast - это то, что я никогда не использую Cast.
30

Используя ответ @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
    Нет необходимости сравнивать listEnumerator.MoveNext () == true. Это все равно что спрашивать компьютер, если правда == правда? :) Просто скажите, если listEnumerator.MoveNext () {}
  • 6
    @ Зести, ты абсолютно прав. Я чувствовал, что в этом случае его легче читать, особенно для людей, которые не привыкли вводить что-либо, кроме меня, как условие.
Показать ещё 2 комментария
24

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

23

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

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;
        }            
    }
}
  • 0
    Это на самом деле не возвращает индекс элемента. Вместо этого он вернет индекс внутри перечислимого списка, который может быть только подсписком списка, что даст вам точные данные только тогда, когда подсписок и список имеют одинаковый размер. По сути, всякий раз, когда в коллекции есть объекты, не относящиеся к запрошенному типу, ваш индекс будет неверным.
  • 7
    @Lucas: нет, но он вернет индекс текущей итерации foreach. Это был вопрос.
21

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

16

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

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

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

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

  • 62
    Две проблемы: 1) Это O(n^2) так как в большинстве реализаций IndexOf это O(n) . 2) Сбой, если в списке есть повторяющиеся элементы.
  • 13
    Примечание: O (n ^ 2) означает, что это может быть катастрофически медленным для большой коллекции.
Показать ещё 4 комментария
10

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

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

  • 4
    Не уверен в тяжелом понижении. Конечно, производительность делает это непомерно высоким, но вы ответили на вопрос!
  • 2
    Другая проблема заключается в том, что это работает, только если элементы в списке являются уникальными.
8

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

Почему 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)
  • 5
    Решение indexOf недопустимо для списка с дубликатами и также очень медленно.
  • 1
    Проблема, которую следует избегать, - это когда вы пересекаете IEnumerable несколько раз, например, чтобы получить количество элементов, а затем каждый элемент. Это имеет значение, когда IEnumerable является результатом запроса к базе данных, например.
6

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

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

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

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}
  • 12
    И теперь алгоритм 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
    //...
}
3

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

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

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

Создание новой функции языка С# от 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 может посыпать синтаксический сахар, чтобы избежать функции расширения

  • 0
    Подождите, значит, эта языковая функция уже существует или она предлагается на будущее?
  • 1
    @Pavel Я обновил ответ, чтобы быть ясным. Этот ответ был предоставлен для противодействия основному ответу, который гласит: «Очевидно, что понятие индекса чуждо понятию перечисления и не может быть сделано».
3

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

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);
    }
}
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);
  • 5
    Это O (n * n).
  • 0
    Вы также можете использовать string.join в c # Например: var joinResult = string.Join (",", listOfNames); '
Показать ещё 2 комментария
3

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

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}
  • 7
    Это предполагает, что в списке нет дубликатов, конечно ...
3

Для интереса, Фил Хаак только что написал пример этого в контексте 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 -Различные контексты.

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));
  • 0
    Я бы использовал struct для пары (index, item).
2

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

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

Как насчет чего-то подобного? Обратите внимание, что 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;
}
  • 0
    Несколько проблем здесь. А) дополнительная скобка. B) строка concat + = за итерацию цикла.
2

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

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

2

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

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

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

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

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

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

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

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

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

я хочу обсудить этот вопрос более теоретически (поскольку у него уже достаточно практических ответов)

.net имеет очень хорошую модель абстракции для групп данных (или коллекций)

  • В самом верху и самом абстрактном у вас есть IEnumerable это просто группа данных, которую вы можете перечислить. Неважно, КАК вы перечисляете, просто вы можете перечислять некоторые данные. И это перечисление сделано совершенно другим объектом, IEnumerator

эти интерфейсы определены следующим образом:

//
// Summary:
//     Exposes an enumerator, which supports a simple iteration over a non-generic collection.
public interface IEnumerable
{
    //
    // Summary:
    //     Returns an enumerator that iterates through a collection.
    //
    // Returns:
    //     An System.Collections.IEnumerator object that can be used to iterate through
    //     the collection.
    IEnumerator GetEnumerator();
}

//
// Summary:
//     Supports a simple iteration over a non-generic collection.
public interface IEnumerator
{
    //
    // Summary:
    //     Gets the element in the collection at the current position of the enumerator.
    //
    // Returns:
    //     The element in the collection at the current position of the enumerator.
    object Current { get; }

    //
    // Summary:
    //     Advances the enumerator to the next element of the collection.
    //
    // Returns:
    //     true if the enumerator was successfully advanced to the next element; false if
    //     the enumerator has passed the end of the collection.
    //
    // Exceptions:
    //   T:System.InvalidOperationException:
    //     The collection was modified after the enumerator was created.
    bool MoveNext();
    //
    // Summary:
    //     Sets the enumerator to its initial position, which is before the first element
    //     in the collection.
    //
    // Exceptions:
    //   T:System.InvalidOperationException:
    //     The collection was modified after the enumerator was created.
    void Reset();
}
  • как вы могли заметить, интерфейс IEnumerator не "знает", что такое индекс, он просто знает, на какой элемент он указывает в данный момент и как перейти к следующему.

  • Теперь есть хитрость: foreach считает каждую входную коллекцию IEnumerable, даже если она является более конкретной реализацией, такой как IList<T> (которая наследуется от IEnumerable), она будет видеть только абстрактный интерфейс IEnumerable.

  • то, что фактически делает foreach, вызывает GetEnumerator для коллекции и вызывает MoveNext пока он не вернет false.

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

лично я бы создал такой метод расширения

public static class Ext
{
    public static void FE<T>(this IEnumerable<T> l, Action<int, T> act)
    {
        int counter = 0;
        foreach (var item in l)
        {
            act(counter, item);
            counter++;
        }
    }
}

и использовать это так

var x = new List<string>() { "hello", "world" };
x.FE((ind, ele) =>
{
    Console.WriteLine($"{ind}: {ele}");
});

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

0

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

Этот вид конструкции полезен, если вы хотите, чтобы ваши взгляды выглядели красиво и чисто в MVC. Например, вместо того, чтобы писать это обычным способом (который трудно отформатировать красиво):

 <%int i=0;
 foreach (var review in Model.ReviewsList) { %>
    <div id="review_<%=i%>">
        <h3><%:review.Title%></h3>                      
    </div>
    <%i++;
 } %>

Вместо этого вы можете написать следующее:

 <%foreach (var review in Model.ReviewsList.WithIndex()) { %>
    <div id="review_<%=LoopHelper.Index()%>">
        <h3><%:review.Title%></h3>                      
    </div>
 <%} %>

Я написал несколько вспомогательных методов, чтобы включить это:

public static class LoopHelper {
    public static int Index() {
        return (int)HttpContext.Current.Items["LoopHelper_Index"];
    }       
}

public static class LoopHelperExtensions {
    public static IEnumerable<T> WithIndex<T>(this IEnumerable<T> that) {
        return new EnumerableWithIndex<T>(that);
    }

    public class EnumerableWithIndex<T> : IEnumerable<T> {
        public IEnumerable<T> Enumerable;

        public EnumerableWithIndex(IEnumerable<T> enumerable) {
            Enumerable = enumerable;
        }

        public IEnumerator<T> GetEnumerator() {
            for (int i = 0; i < Enumerable.Count(); i++) {
                HttpContext.Current.Items["LoopHelper_Index"] = i;
                yield return Enumerable.ElementAt(i);
            }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }

В не-веб-среде вы можете использовать static вместо HttpContext.Current.Items.

Это, по сути, глобальная переменная, поэтому вы не можете иметь несколько вложенных циклов InIndex, но это не является серьезной проблемой в этом случае.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню