Warum kann ich eine Liste wie ein Array in C # initialisieren?

131

Heute war ich überrascht, dass ich in C # Folgendes tun kann:

List<int> a = new List<int> { 1, 2, 3 };

Warum kann ich das tun? Welcher Konstruktor heißt? Wie kann ich das mit meinen eigenen Klassen machen? Ich weiß, dass dies der Weg ist, um Arrays zu initialisieren, aber Arrays sind Sprachelemente und Listen sind einfache Objekte ...

Ignacio Soler Garcia
quelle
1
Diese Frage kann hilfreich sein: stackoverflow.com/questions/1744967/…
Bob Kaufman
10
Ziemlich genial, oder? Sie können auch sehr ähnlichen Code verwenden, um Wörterbücher zu initialisieren:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig
Aus einer anderen Sicht erinnert uns diese Array-ähnliche Initialisierungssyntax daran, dass der zugrunde liegende Datentyp von a List<int>tatsächlich nur ein Array ist. Ich hasse das C # -Team dafür, dass sie es nicht benennen, ArrayList<T>was so offensichtlich und natürlich klingt.
RBT

Antworten:

183

Dies ist Teil der Syntax der Auflistungsinitialisierung in .NET. Sie können diese Syntax für jede Sammlung verwenden, die Sie erstellen, solange:

  • Es implementiert IEnumerable(vorzugsweise IEnumerable<T>)

  • Es hat eine Methode namens Add(...)

Was passiert, ist, dass der Standardkonstruktor aufgerufen wird und dann Add(...)für jedes Mitglied des Initialisierers aufgerufen wird.

Somit sind diese beiden Blöcke ungefähr identisch:

List<int> a = new List<int> { 1, 2, 3 };

Und

List<int> temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
List<int> a = temp;

Sie können einen alternativen Konstruktor aufrufen, wenn Sie möchten, um beispielsweise eine Überdimensionierung List<T>während des Wachstums zu vermeiden usw.:

// Notice, calls the List constructor that takes an int arg
// for initial capacity, then Add()'s three items.
List<int> a = new List<int>(3) { 1, 2, 3, }

Beachten Sie, dass die Add()Methode kein einzelnes Element annehmen muss, z. B. die Add()Methode für Dictionary<TKey, TValue>zwei Elemente:

var grades = new Dictionary<string, int>
    {
        { "Suzy", 100 },
        { "David", 98 },
        { "Karen", 73 }
    };

Ist ungefähr identisch mit:

var temp = new Dictionary<string, int>();
temp.Add("Suzy", 100);
temp.Add("David", 98);
temp.Add("Karen", 73);
var grades = temp;

Um dies zu Ihrer eigenen Klasse hinzuzufügen, müssen Sie, wie erwähnt, lediglich IEnumerable(vorzugsweise IEnumerable<T>) implementieren und eine oder mehrere Add()Methoden erstellen :

public class SomeCollection<T> : IEnumerable<T>
{
    // implement Add() methods appropriate for your collection
    public void Add(T item)
    {
        // your add logic    
    }

    // implement your enumerators for IEnumerable<T> (and IEnumerable)
    public IEnumerator<T> GetEnumerator()
    {
        // your implementation
    }

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

Dann können Sie es genau wie die BCL-Sammlungen verwenden:

public class MyProgram
{
    private SomeCollection<int> _myCollection = new SomeCollection<int> { 13, 5, 7 };    

    // ...
}

(Weitere Informationen finden Sie im MSDN. )

James Michael Hare
quelle
29
Gute Antwort, obwohl ich feststelle, dass es nicht ganz richtig ist, in Ihrem ersten Beispiel "genau identisch" zu sagen. Es ist genau identisch mit List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; Das heißt, die aVariable wird erst initialisiert, nachdem alle Adds aufgerufen wurden. Andernfalls wäre es legal, so etwas zu tun, List<int> a = new List<int>() { a.Count, a.Count, a.Count };was eine verrückte Sache ist.
Eric Lippert
1
@ user606723: Es gibt keinen wirklichen Unterschied zwischen List<int> a; a = new List<int>() { a.Count };undList<int> a = new List<int>() { a.Count };
Joren
4
@Joren: Du bist richtig; Tatsächlich besagt die C # -Spezifikation, dass dies T x = y;dasselbe ist wie T x; x = y;: Diese Tatsache kann zu einigen merkwürdigen Situationen führen. Zum Beispiel int x = M(out x) + x;ist vollkommen legal, weil int x; x = M(out x) + x;es legal ist.
Eric Lippert
1
@JamesMichaelHare ist nicht erforderlich zu implementieren IEnumerable<T>; Das Nicht-Generische IEnumerablereicht aus, um die Verwendung der Syntax des Collection-Initialisierers zu ermöglichen.
Phoog
2
Erics Argument ist wichtig, wenn Sie eine Sammlung verwenden, die IDisposable implementiert. using (var x = new Something{ 1, 2 })Das Objekt wird nicht entsorgt, wenn einer der AddAufrufe fehlschlägt.
porges
11

Es ist so genannter syntaktischer Zucker .

List<T> ist die "einfache" Klasse, aber der Compiler behandelt sie speziell, um Ihnen das Leben zu erleichtern.

Dieser wird als Sammlungsinitialisierer bezeichnet . Sie müssen implementieren IEnumerable<T>und Addmethodisieren.

Krizz
quelle
8

Gemäß der C # Version 3.0-Spezifikation "Das Auflistungsobjekt, auf das ein Auflistungsinitialisierer angewendet wird, muss von einem Typ sein, der System.Collections.Generic.ICollection für genau ein T implementiert."

Diese Informationen scheinen jedoch zum Zeitpunkt dieses Schreibens ungenau zu sein. siehe Eric Lipperts Klarstellung in den Kommentaren unten.

Olivier Jacot-Descombes
quelle
10
Diese Seite ist nicht korrekt. Vielen Dank, dass Sie mich darauf aufmerksam gemacht haben. Das muss eine vorläufige Dokumentation vor der Veröffentlichung sein, die irgendwie nie gelöscht wurde. Das ursprüngliche Design sollte ICollection erfordern, aber das ist nicht das endgültige Design, für das wir uns entschieden haben.
Eric Lippert
1
Danke für die Klarstellung.
Olivier Jacot-Descombes
6

Es funktioniert dank Sammlungsinitialisierern , für die die Sammlung grundsätzlich eine Add-Methode implementieren muss, und die die Arbeit für Sie erledigt.

vc 74
quelle
6

Eine weitere coole Sache bei Sammlungsinitialisierern ist, dass Sie mehrere AddMethodenüberladungen haben und sie alle im selben Initialisierer aufrufen können! Zum Beispiel funktioniert dies:

public class MyCollection<T> : IEnumerable<T>
{
    public void Add(T item, int number)
    {

    }
    public void Add(T item, string text) 
    {

    }
    public bool Add(T item) //return type could be anything
    {

    }
}

var myCollection = new MyCollection<bool> 
{
    true,
    { false, 0 },
    { true, "" },
    false
};

Es ruft die richtigen Überladungen auf. Außerdem sucht es nur nach der Methode mit dem Namen Add, der Rückgabetyp kann alles sein.

nawfal
quelle
0

Die Array-ähnliche Syntax wird in einer Reihe von Add()Aufrufen gedreht.

Um dies in einem viel interessanteren Beispiel zu sehen, betrachten Sie den folgenden Code, in dem ich zwei interessante Dinge tue, die in C # zuerst illegal klingen: 1) Festlegen einer schreibgeschützten Eigenschaft, 2) Festlegen einer Liste mit einem Array wie dem Initialisierer.

public class MyClass
{   
    public MyClass()
    {   
        _list = new List<string>();
    }
    private IList<string> _list;
    public IList<string> MyList 
    { 
        get
        { 
            return _list;
        }
    }
}
//In some other method
var sample = new MyClass
{
    MyList = {"a", "b"}
};

Dieser Code funktioniert einwandfrei, obwohl 1) MyList schreibgeschützt ist und 2) ich eine Liste mit Array-Initialisierer erstellt habe.

Der Grund, warum dies funktioniert, liegt darin, dass der Compiler in Code, der Teil eines Objektinitialisierers ist, eine {}ähnliche Syntax immer in eine Reihe von Add()Aufrufen umwandelt, die selbst in einem schreibgeschützten Feld vollkommen legal sind.

yoel halb
quelle