В каком порядке C # для каждого цикла перебирает List <T>?

84

Мне было интересно узнать, в каком порядке цикл foreach в C # проходит через System.Collections.Generic.List<T>объект.

Я нашел еще один вопрос по той же теме, но не думаю, что он удовлетворит меня ответом на мой вопрос.

Кто-то заявляет, что порядок не определен. Но, как утверждает кто-то другой, порядок обхода массива фиксирован (от 0 до Length-1). 8.8.4 Оператор foreach

Также было сказано, что то же самое верно для любых стандартных классов с порядком (например List<T>). Я не могу найти никакой документации, подтверждающей это. Итак, насколько я знаю, сейчас это может работать так, но, возможно, в следующей версии .NET все будет по-другому (хотя это может быть маловероятно).

Я также безуспешно смотрел List(t).Enumeratorдокументацию.

Другой связанный с этим вопрос гласит, что для Java это специально упоминается в документации:

List.iterator()возвращает итератор по элементам в этом списке в правильной последовательности. "

Я ищу что-то подобное в документации по C #.

Заранее спасибо.

Изменить: Спасибо вам за все ваши ответы (удивительно, как быстро я получил так много ответов). Из всех ответов я понимаю, что List<T>всегда выполняется итерация в порядке индексации. Но я все же хотел бы видеть четкую документацию, заявляющую об этом, аналогичную документации по JavaList .

Маттейс Весселс
источник

Ответы:

14

На странице Microsoft Reference Source для List<T>Enumerator явно указано, что итерация выполняется от 0 до Length-1:

internal Enumerator(List<T> list) {
    this.list = list;
    index = 0;
    version = list._version;
    current = default(T);
}

public bool MoveNext() {

    List<T> localList = list;

    if (version == localList._version && ((uint)index < (uint)localList._size)) 
    {                                                     
        current = localList._items[index];                    
        index++;
        return true;
    }
    return MoveNextRare();
}

Надеюсь, это все еще актуально для кого-то

Апокровский
источник
102

В основном это до IEnumeratorреализации - но для List<T>него всегда будет идти в естественном порядке списка, то есть в том же порядке , как индексатор: list[0], list[1], и list[2]т.д.

Я не верю, что это явно задокументировано - по крайней мере, я не нашел такой документации - но я думаю, что вы можете рассматривать это как гарантированно. Любое изменение этого порядка приведет к бессмысленному нарушению всех видов кода. На самом деле, я был бы удивлен, если бы увидел, что любая реализация этого IList<T>не подчиняется. По общему признанию, было бы неплохо увидеть это специально задокументированным ...

Джон Скит
источник
Я буквально только что проверил ответ 5 секунд назад и собирался опубликовать что-то подобное. Ты слишком быстр!
Michael G
Спасибо за ответы. Есть ли какая-то документация, которая это гарантирует?
Matthijs Wessels,
1
@Michael G: Я бы не стал полагаться на пример кода MSDN в качестве документации. Я видел в нем слишком много ужасных ошибок.
Джон Скит,
1
может ли кто-нибудь дать такой же ответ относительно массива?
yazanpro
12
@yazanpro: foreachопределенно будет повторяться по порядку с массивом.
Джон Скит
8

В вашей ссылке принятый ответ указывает в Спецификации языка C # версии 3.0, стр. 240 :

Порядок, в котором foreach проходит элементы массива, следующий: для одномерных массивов элементы проходят в порядке возрастания индекса, начиная с индекса 0 и заканчивая индексом Length - 1. Для многомерных массивов элементы проходят таким образом, что сначала увеличиваются индексы самого правого измерения, затем следующего левого измерения и так далее влево. В следующем примере каждое значение двухмерного массива выводится в порядке элементов:

using System;
class Test
{
  static void Main() {
      double[,] values = {
          {1.2, 2.3, 3.4, 4.5},
          {5.6, 6.7, 7.8, 8.9}
      };
      foreach (double elementValue in values)
          Console.Write("{0} ", elementValue);
      Console.WriteLine();
  }
}

Полученный результат будет следующим: 1,2 2,3 3,4 4,5 5,6 6,7 7,8 8,9 В примере

int[] numbers = { 1, 3, 5, 7, 9 };
foreach (var n in numbers) Console.WriteLine(n);
the type of n is inferred to be int, the element type of numbers.
RvdK
источник
2
Да, но это для массива. Это также автоматически выполняется для класса List <T>?
Matthijs Wessels,
2
List <T> использует массив в качестве резервного хранилища. Так да.
Джоэл Мюллер,
9
Но это деталь реализации. List <T> не требуется для использования массива в качестве резервного хранилища.
Эрик Липперт,
3
Я хотел бы указать на документацию для List <T> (в качестве примера кода): «Класс List <(Of <(T>)>) является универсальным эквивалентом класса ArrayList. Он реализует IList <(Of <(T>)>) универсальный интерфейс с использованием массива , размер которого динамически увеличивается по мере необходимости ». (курсив мой)
RCIX
Хм, я думаю, тогда он всегда использует массив. Но все же означает ли это, что он не может вернуть обратный перечислитель?
Matthijs Wessels
4

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

Если вы используете стандартную индексируемую коллекцию (например, List), то она будет проходить по коллекции, начиная с индекса 0 и двигаясь вверх.

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

Это объясняет, как Enumerator работает для общего списка. Сначала текущий элемент не определен и использует MoveNext для перехода к следующему элементу.

Если вы читаете MoveNext, это означает, что он начнется с первого элемента коллекции и оттуда перейдет к следующему, пока не достигнет конца коллекции.

Брендан Энрик
источник
Спасибо за ответ и добавление (все отвечают так быстро, что я едва успеваю). Я тоже это читал. Возможно, я просто <слово, чтобы указать кого-то, кто хочет быть слишком точным>, но я чувствую, что, когда они говорят «первый элемент», они имеют в виду первый элемент, который будет повторяться, а не элемент, который будет первым, согласно к порядку итерированного класса.
Matthijs Wessels,
Что ж, если так важно, чтобы вы были уверены, что все идет так, как вы ожидаете, лучший способ - это сделать то, что я сказал, и реализовать IEnumerable самостоятельно.
Брендан Энрик,
1

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

Если ваша программа зависит от порядка, вы можете отсортировать ее перед просмотром списка.

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

Броам
источник
1

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

Использование LINQ для изменения порядка

         DataGridViewColumn[] gridColumns = new DataGridViewColumn[dataGridView1.Columns.Count];
         dataGridView1.Columns.CopyTo(gridColumns, 0); //This created a list of columns

         gridColumns = (from n in gridColumns
                        orderby n.DisplayIndex descending
                        select n).ToArray(); //This then changed the order based on the displayindex
KillJoy
источник
Я не понимаю, как это связано с вопросом. В вашем коде даже не используется расширение List. Я думаю, вы неправильно поняли, что я имел в виду под словом «список».
Matthijs Wessels,
Кроме того, почему вы сначала копируете содержимое Columnsв gridColumns? Я думаю, что вы также могли бы просто сделать:DataGridViewColumn[] gridColumns = dataGridView1.Columns.OrderByDescending(n => n.DisplayIndex).ToArray();
Matthijs Wessels
@MatthijsWessels Нетрудно было бы изменить это, чтобы использовать List, если хотите.
Остин Хенли,
@AustinHenley Вы могли бы выполнить foreach на IOrderedEnumerable, но вопрос не в этом. Вы также можете сделать ToList вместо ToArray, но тогда у вас снова будет List, и вопрос о том, будет ли foreach затем перебирать элементы в указанном порядке, все еще остается без ответа.
Matthijs Wessels,