W jakiej kolejności C # for each loop iteruje po List <T>?

84

Zastanawiałem się nad kolejnością, w jakiej pętla foreach w języku C # przechodzi przez System.Collections.Generic.List<T>obiekt.

Znalazłem kolejne pytanie na ten sam temat, ale nie czuję, że odpowiada na moje pytanie w sposób satysfakcjonujący.

Ktoś twierdzi, że nie określono żadnego porządku. Ale jak twierdzi ktoś inny, kolejność przechodzenia przez tablicę jest stała (od 0 do Length-1). 8.8.4 Oświadczenie foreach

Mówiono też, że to samo dotyczy wszystkich klas standardowych z kolejnością (np List<T>.). Nie mogę znaleźć żadnej dokumentacji, która by to potwierdzała. Więc z tego co wiem, teraz może tak działać, ale może w następnej wersji .NET będzie inaczej (nawet jeśli może to być mało prawdopodobne).

Spojrzałem też na List(t).Enumeratordokumentację bez powodzenia.

Inne powiązane pytanie stwierdza, że ​​w przypadku języka Java jest to wyraźnie wymienione w dokumentacji:

List.iterator()zwraca iterator po elementach tej listy w odpowiedniej kolejności. "

Szukam czegoś takiego w dokumentacji C #.

Z góry dziękuję.

Edycja: Dziękuję wszystkim za wszystkie odpowiedzi (niesamowite, jak szybko otrzymałem tak wiele odpowiedzi). Ze wszystkich odpowiedzi rozumiem, że List<T>zawsze zmienia się kolejność indeksowania. Ale nadal chciałbym zobaczyć wyraźny spokój dokumentacji stwierdzającej to, podobnie jak w dokumentacji JavaList .

Matthijs Wessels
źródło

Odpowiedzi:

14

Na stronie Microsoft Reference Source dla List<T>Enumerator jest wyraźnie stwierdzone, że iteracja jest wykonywana od 0 do 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();
}

Mam nadzieję, że to nadal jest dla kogoś istotne

apokrovskyi
źródło
102

Zasadniczo jest to aż do IEnumeratorrealizacji - ale na List<T>to zawsze iść w naturalnym porządkiem listy, czyli takiej samej kolejności jak w indekser: list[0], list[1], list[2]itd.

Nie wierzę, że jest to wyraźnie udokumentowane - przynajmniej nie znalazłem takiej dokumentacji - ale myślę, że można to traktować jako gwarantowane. Jakakolwiek zmiana w tej kolejności bezcelowo złamałaby wszystkie rodzaje kodu. W rzeczywistości byłbym zaskoczony, widząc, że jakakolwiek implementacja jest IList<T>niezgodna z tym. Trzeba przyznać, że fajnie byłoby zobaczyć to szczegółowo udokumentowane ...

Jon Skeet
źródło
Dosłownie właśnie sprawdziłem odpowiedź 5 sekund temu i miałem zamiar opublikować coś podobnego. Jesteś za szybki!
Michael G
Dziękuję za twoje odpowiedzi. Czy istnieje dokumentacja, która to gwarantuje?
Matthijs Wessels
1
@Michael G: Nie polegałbym jednak na przykładowym kodzie MSDN jako dokumentacji. Widziałem w nim zbyt wiele przerażających błędów.
Jon Skeet
1
czy ktoś może udzielić tej samej odpowiedzi dotyczącej tablicy?
yazanpro
12
@yazanpro: na foreachpewno będzie iterować w kolejności z tablicą.
Jon Skeet,
8

W linku zaakceptowana odpowiedź stwierdza w specyfikacji języka C # w wersji 3.0, strona 240 :

Kolejność, w jakiej każdy przechodzi przez elementy tablicy, jest następująca: W przypadku tablic jednowymiarowych elementy przechodzą w rosnącej kolejności indeksów, zaczynając od indeksu 0, a kończąc na indeksie Długość - 1. W przypadku tablic wielowymiarowych przechodzą elementy tak, że indeksy wymiaru znajdującego się najbardziej na prawo są zwiększane najpierw, potem następny lewy wymiar i tak dalej w lewo. Poniższy przykład wyświetla każdą wartość w dwuwymiarowej tablicy w kolejności elementów:

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

Uzyskany wynik jest następujący: 1,2 2,3 3,4 4,5 5,6 6,7 7,8 8,9 W tym przykładzie

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
źródło
2
Tak, ale to jest dla tablicy. Czy jest to również automatycznie zachowywane dla klasy List <T>?
Matthijs Wessels
2
List <T> używa tablicy jako magazynu zapasowego. Więc tak.
Joel Mueller
9
Ale to szczegół implementacji. List <T> nie musi używać tablicy jako magazynu zapasowego.
Eric Lippert
3
Chciałbym zwrócić uwagę na dokumentację dla List <T> (nto przykładowy kod): "Klasa List <(Of <(T>)>) jest generycznym odpowiednikiem klasy ArrayList. Implementuje IList <(Of <(T>)>) interfejs ogólny używający tablicy, której rozmiar jest dynamicznie zwiększany zgodnie z wymaganiami. " (moje podkreślenie)
RCIX
Hmm, myślę, że wtedy zawsze używa tablicy. Ale czy to oznacza, że ​​nie może zwrócić odwróconego modułu wyliczającego?
Matthijs Wessels,
4

Kolejność jest definiowana przez iterator używany do przechodzenia przez kolekcję danych przy użyciu pętli foreach.

Jeśli używasz standardowej kolekcji, którą można indeksować (na przykład listy), przejdzie ona do kolekcji, zaczynając od indeksu 0 i przesuwając się w górę.

Jeśli chcesz kontrolować kolejność, możesz albo kontrolować sposób obsługi iteracji kolekcji, implementując własny IEnumerable , albo sortować listę tak, jak chcesz, przed wykonaniem pętli foreach.

To wyjaśnia, jak Enumerator działa dla ogólnej listy. Początkowo bieżący element jest niezdefiniowany i używa MoveNext, aby przejść do następnego elementu.

Jeśli przeczytasz MoveNext , oznacza to, że rozpocznie się od pierwszego elementu kolekcji, a stamtąd przejdzie do następnego, aż osiągnie koniec kolekcji.

Brendan Enrick
źródło
Dzięki za odpowiedź i dodanie (wszyscy tak szybko odpowiadają, ledwo nadążam). Też to czytałem. Może po prostu jestem <słowem na określenie kogoś, kto chce być zbyt precyzyjny>, ale czuję, że kiedy mówią „pierwszy element”, mają na myśli pierwszy element, który będzie iterowany, a nie element, który jest pierwszy według do kolejności klasy iterowanej.
Matthijs Wessels
Cóż, jeśli ważne jest, aby mieć pewność, że wszystko przebiega zgodnie z oczekiwaniami, najlepszym sposobem jest zrobienie tego, co powiedziałem i samodzielne zaimplementowanie IEnumerable.
Brendan Enrick
1

Listy wydają się zwracać elementy w kolejności, w jakiej znajdują się w magazynie zapasowym - więc jeśli zostaną dodane do listy w ten sposób, zostaną zwrócone w ten sposób.

Jeśli Twój program zależy od kolejności, możesz chcieć posortować go przed przejściem przez listę.

To trochę głupie w przypadku wyszukiwania liniowego - ale jeśli potrzebujesz zamówienia w określony sposób, najlepszym rozwiązaniem jest wykonanie elementów w tej kolejności.

Broam
źródło
1

Po prostu musiałem zrobić coś podobnego do szybkiego hackowania kodu, chociaż nie zadziałało to w przypadku tego, co próbowałem zrobić, zmieniło kolejność listy dla mnie.

Używanie LINQ do zmiany kolejności

         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
Człowiek psujący zabawę
źródło
Nie rozumiem, jak to się ma do pytania. Twój kod nie używa nawet List. Myślę, że źle zrozumiałeś, co mam na myśli mówiąc o „liście”.
Matthijs Wessels,
Dlaczego najpierw kopiujesz zawartość Columnsdo gridColumns? Myślę, że możesz też po prostu zrobić:DataGridViewColumn[] gridColumns = dataGridView1.Columns.OrderByDescending(n => n.DisplayIndex).ToArray();
Matthijs Wessels
@MatthijsWessels Jeśli chcesz, nie byłoby trudno to zmienić, aby używać listy.
Austin Henley
@AustinHenley Mógłbyś zrobić foreach na IOrderedEnumerable, ale nie o to chodzi. Możesz także zrobić ToList zamiast ToArray, ale wtedy po prostu masz ponownie Listę i pytanie, czy foreach następnie zapętli elementy w określonej kolejności, nadal pozostaje bez odpowiedzi.
Matthijs Wessels,