Как вы получаете индекс текущей итерации цикла foreach? - PullRequest
789 голосов
/ 04 сентября 2008

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

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

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

Ответы [ 34 ]

0 голосов
/ 20 мая 2019

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

 ListValues.Select((x, i) => new { Value = x, Index = i }).ToList().ForEach(element =>
        {
          //element.Index
       //element.Value

        });
0 голосов
/ 09 апреля 2013

Вот еще одно решение этой проблемы, с акцентом на поддержание синтаксиса как можно ближе к стандартному 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.

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

0 голосов
/ 15 декабря 2018

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

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

  • В самом верху и самом абстрактном у вас есть 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 голосов
/ 04 ноября 2015

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

// Untested
for (int i = 0; i < collection.Count; i++)
{
    Console.WriteLine("My index is " + i);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...