In welcher Reihenfolge iteriert ein C # für jede Schleife über eine Liste <T>?

81

Ich habe mich über die Reihenfolge gewundert, in der eine foreach-Schleife in C # ein System.Collections.Generic.List<T>Objekt durchläuft .

Ich habe eine andere Frage zum gleichen Thema gefunden, aber ich habe nicht das Gefühl, dass sie meine Frage zu meiner Zufriedenheit beantwortet.

Jemand gibt an, dass keine Reihenfolge definiert ist. Aber wie jemand anderes sagt, ist die Reihenfolge, in der ein Array durchlaufen wird, fest (von 0 bis Länge 1). 8.8.4 Die foreach-Anweisung

Es wurde auch gesagt, dass das Gleiche für alle Standardklassen mit einer Bestellung gilt (z List<T>. B. ). Ich kann keine Dokumentation finden, um das zu sichern. Soweit ich weiß, könnte es jetzt so funktionieren, aber vielleicht wird es in der nächsten .NET-Version anders sein (auch wenn es unwahrscheinlich ist).

Ich habe mir auch die List(t).EnumeratorDokumentation ohne Glück angesehen.

Eine andere verwandte Frage besagt, dass Java speziell in der Dokumentation erwähnt wird:

List.iterator()Gibt einen Iterator über die Elemente in dieser Liste in der richtigen Reihenfolge zurück. "

Ich suche so etwas in der C # -Dokumentation.

Danke im Voraus.

Bearbeiten: Vielen Dank für all Ihre Antworten (erstaunlich, wie schnell ich so viele Antworten erhalten habe). Was ich aus allen Antworten verstehe, ist, dass List<T>es immer in der Reihenfolge seiner Indizierung iteriert. Trotzdem würde ich mir eine klare Dokumentation wünschen, die dies ähnlich wie die Java-Dokumentation auf beschreibtList .

Matthijs Wessels
quelle

Antworten:

13

Auf der Microsoft-Referenzquellenseite für List<T>Enumerator wird ausdrücklich angegeben, dass die Iteration von 0 bis Länge 1 erfolgt:

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

Hoffe, es ist immer noch relevant für jemanden

apokrovskyi
quelle
102

Im Grunde ist es auf die oben IEnumeratorUmsetzung - aber für einen List<T>wird es immer in der natürlichen Reihenfolge der Liste gehen, dh die gleiche Reihenfolge wie die Indexer: list[0], list[1], list[2]usw.

Ich glaube nicht, dass es explizit dokumentiert ist - zumindest habe ich eine solche Dokumentation nicht gefunden -, aber ich denke, Sie können sie als garantiert behandeln. Jede Änderung dieser Reihenfolge würde sinnlos alle Arten von Code zerstören. Tatsächlich wäre ich überrascht zu sehen, welche Implementierung IList<T>dies missachtete. Zugegeben, es wäre schön, wenn es speziell dokumentiert wäre ...

Jon Skeet
quelle
Ich habe vor 5 Sekunden buchstäblich nur nach einer Antwort gesucht und wollte etwas Ähnliches posten. Du bist zu schnell!
Michael G
Vielen Dank für Ihre Antworten. Gibt es eine Dokumentation, die dies garantiert?
Matthijs Wessels
1
@ Michael G: Ich würde mich jedoch nicht auf den MSDN-Beispielcode als Dokumentation verlassen. Ich habe zu viele entsetzliche Fehler darin gesehen.
Jon Skeet
1
Kann jemand bitte die gleiche Antwort bezüglich des Arrays geben?
Yazanpro
12
@yazanpro: foreachwird definitiv in der Reihenfolge mit einem Array iterieren.
Jon Skeet
8

In Ihrem Link heißt es in der akzeptierten Antwort in C # Language Specification Version 3.0, Seite 249 :

Die Reihenfolge, in der foreach die Elemente eines Arrays durchläuft, lautet wie folgt: Bei eindimensionalen Arrays werden Elemente in aufsteigender Indexreihenfolge durchlaufen, beginnend mit Index 0 und endend mit Indexlänge - 1. Bei mehrdimensionalen Arrays werden Elemente durchlaufen so dass die Indizes der Dimension ganz rechts zuerst erhöht werden, dann die nächste Dimension links und so weiter nach links. Im folgenden Beispiel wird jeder Wert in einem zweidimensionalen Array in Elementreihenfolge ausgedruckt:

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

Die erzeugte Ausgabe ist wie folgt: 1.2 2.3 3.4 4.5 5.6 6.7 7.8 8.9 Im Beispiel

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
quelle
2
Ja, aber dies ist für ein Array. Gilt es auch automatisch für die List <T> -Klasse?
Matthijs Wessels
2
List <T> verwendet ein Array für seinen Sicherungsspeicher. Also ja.
Joel Mueller
9
Aber das ist ein Implementierungsdetail. List <T> ist nicht erforderlich , um ein Array als Hintergrundspeicher zu verwenden.
Eric Lippert
3
Ich möchte auf die Dokumentation für List <T> (bis zum Beispielcode) hinweisen: "Die List <(Of <(T>)>) - Klasse ist das generische Äquivalent der ArrayList-Klasse. Sie implementiert die IList <(Of) <(T>)>) generische Schnittstelle unter Verwendung eines Arrays, dessen Größe nach Bedarf dynamisch erhöht wird. " (Hervorhebung von mir)
RCIX
Hmm, ich denke, es wird dann immer ein Array verwendet. Bedeutet das jedoch, dass kein umgekehrter Enumerator zurückgegeben werden kann?
Matthijs Wessels
4

Die Reihenfolge wird durch den Iterator definiert, der zum Durchlaufen einer Datensammlung mithilfe einer foreach-Schleife verwendet wird.

Wenn Sie eine indexierbare Standardsammlung verwenden (z. B. eine Liste), wird die Sammlung beginnend mit Index 0 durchlaufen und nach oben verschoben.

Wenn Sie die Reihenfolge steuern müssen, können Sie entweder steuern, wie die Iteration der Sammlung behandelt wird, indem Sie Ihre eigene IEnumerable implementieren , oder Sie können die Liste nach Ihren Wünschen sortieren, bevor Sie die foreach-Schleife ausführen.

Dies erklärt, wie Enumerator für generische Listen funktioniert. Das aktuelle Element ist zunächst undefiniert und verwendet MoveNext, um zum nächsten Element zu gelangen.

Wenn Sie MoveNext lesen, bedeutet dies, dass es mit dem ersten Element der Sammlung beginnt und von dort zum nächsten wechselt, bis es das Ende der Sammlung erreicht.

Brendan Enrick
quelle
Vielen Dank für die Antwort und den Zusatz (Alle antworten so schnell, dass ich kaum mithalten kann). Das habe ich auch gelesen. Vielleicht bin ich nur ein <Wort, um jemanden zu spezifizieren, der zu präzise sein möchte>, aber ich habe das Gefühl, wenn sie "erstes Element" sagen, meinen sie das erste Element, das iteriert werden soll, nicht das Element, das zuerst entspricht in der Reihenfolge der iterierten Klasse.
Matthijs Wessels
Wenn es so wichtig ist, dass Sie sicher sind, dass es so läuft, wie Sie es erwarten, ist es am besten, das zu tun, was ich gesagt habe, und IEnumerable selbst zu implementieren.
Brendan Enrick
1

Listen scheinen die Artikel in einer Reihenfolge zurückzugeben, in der sie sich im Hintergrundspeicher befinden. Wenn sie also auf diese Weise zur Liste hinzugefügt werden, werden sie auf diese Weise zurückgegeben.

Wenn Ihr Programm von der Reihenfolge abhängt, können Sie es sortieren, bevor Sie die Liste durchlaufen.

Es ist etwas albern für lineare Suchen - aber wenn Sie die Bestellung auf eine bestimmte Weise benötigen, ist es am besten, die Artikel in dieser Reihenfolge zu machen.

Broam
quelle
1

Ich musste nur etwas Ähnliches wie einen schnellen Hack von Code tun, obwohl es für das, was ich versuchte, nicht funktionierte, aber die Liste für mich neu ordnete.

Verwenden von LINQ zum Ändern der Reihenfolge

         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
quelle
Ich verstehe nicht, wie das mit der Frage zusammenhängt. Ihr Code verwendet nicht einmal a List. Ich denke, Sie haben falsch verstanden, was ich mit "Liste" gemeint habe.
Matthijs Wessels
Warum kopieren Sie zuerst den Inhalt von Columnsnach gridColumns? Ich denke, dass Sie auch einfach tun könnten:DataGridViewColumn[] gridColumns = dataGridView1.Columns.OrderByDescending(n => n.DisplayIndex).ToArray();
Matthijs Wessels
@MatthijsWessels Es wäre nicht schwer, dies zu ändern, um List zu verwenden, wenn Sie möchten.
Austin Henley
@AustinHenley Sie könnten einen Foreach für die IOrderedEnumerable durchführen, aber darum geht es bei der Frage nicht. Sie können auch ToList anstelle von ToArray verwenden, aber dann haben Sie einfach wieder eine Liste und die Frage, ob ein foreach dann die Elemente in der angegebenen Reihenfolge durchläuft, bleibt unbeantwortet.
Matthijs Wessels