Perché posso inizializzare un elenco come un array in C #?


131

Oggi sono stato sorpreso di scoprire che in C # posso fare:

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

Perché posso farlo? Quale costruttore si chiama? Come posso farlo con le mie lezioni? So che questo è il modo di inizializzare le matrici ma le matrici sono voci di linguaggio e le liste sono oggetti semplici ...


1
Questa domanda può essere di aiuto: stackoverflow.com/questions/1744967/…
Bob Kaufman,

10
Abbastanza fantastico eh? Puoi anche fare un codice molto simile per inizializzare i dizionari:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig

Da un'altra prospettiva questo array come la sintassi di inizializzazione ci dà un promemoria che il tipo di dati sottostante di a List<int>è in realtà solo un array. Odio il team C # per il fatto che non lo ArrayList<T>nominano, il che sembra così ovvio e naturale.
RBT

Risposte:


183

Questo fa parte della sintassi dell'inizializzatore di raccolte in .NET. Puoi utilizzare questa sintassi su qualsiasi raccolta che crei purché:

  • Implementa IEnumerable(preferibilmente IEnumerable<T>)

  • Ha un metodo chiamato Add(...)

Ciò che accade è il costruttore predefinito che viene chiamato e quindi Add(...)viene chiamato per ciascun membro dell'inizializzatore.

Pertanto, questi due blocchi sono approssimativamente identici:

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

E

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

È possibile chiamare un costruttore alternativo, se si desidera, ad esempio per evitare sovra-dimensionamento della List<T>durante la crescita, ecc:

// 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, }

Si noti che il Add()metodo non deve prendere un singolo elemento, ad esempio il Add()metodo per Dictionary<TKey, TValue>accetta due elementi:

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

È approssimativamente identico a:

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

Quindi, per aggiungere questo alla tua classe, tutto ciò che devi fare, come detto, è implementare IEnumerable(di nuovo, preferibilmente IEnumerable<T>) e creare uno o più Add()metodi:

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

Quindi puoi usarlo proprio come fanno le raccolte BCL:

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

    // ...
}

(Per ulteriori informazioni, consultare MSDN )


29
Buona risposta, anche se noto che non è del tutto preciso dire "esattamente identico" nel tuo primo esempio. È esattamente identico a List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; Cioè, la avariabile non viene inizializzata fino a quando non vengono chiamate tutte le aggiunte. Altrimenti sarebbe legale fare qualcosa del genere List<int> a = new List<int>() { a.Count, a.Count, a.Count };che è una cosa folle da fare.
Eric Lippert,

1
@ user606723: Non c'è alcuna differenza reale tra List<int> a; a = new List<int>() { a.Count };eList<int> a = new List<int>() { a.Count };
Joren,

4
@Joren: hai ragione; infatti la specifica C # afferma che T x = y;è la stessa di T x; x = y;, Questo fatto può portare ad alcune situazioni strane. Ad esempio, int x = M(out x) + x;è perfettamente legale perché int x; x = M(out x) + x;è legale.
Eric Lippert,

1
@JamesMichaelHare non è necessario implementare IEnumerable<T>; il non generico IEnumerableè sufficiente per consentire l'uso della sintassi dell'inizializzatore di raccolta.
phoog

2
Il punto di Eric è importante se stai usando una sorta di collezione che implementa IDisposable. using (var x = new Something{ 1, 2 })non disporrà l'oggetto se una delle Addchiamate fallisce.
Porges,


8

Secondo le specifiche della versione 3.0 di C # "L'oggetto di raccolta a cui viene applicato un inizializzatore di raccolta deve essere di un tipo che implementa System.Collections.Generic.ICollection esattamente per un T."

Tuttavia, queste informazioni sembrano essere inaccurate al momento della stesura di questo documento; vedere i chiarimenti di Eric Lippert nei commenti qui sotto.


10
Quella pagina non è precisa. Grazie per avermelo fatto notare. Deve essere una documentazione preliminare alla pre-release che in qualche modo non è mai stata cancellata. Il progetto originale doveva richiedere ICollection, ma non è quello finale su cui ci siamo basati.
Eric Lippert,

1
Grazie per il chiarimento.
Olivier Jacot-Descombes,


6

Un'altra cosa interessante degli inizializzatori di raccolta è che puoi avere più sovraccarichi di Addmetodo e puoi chiamarli tutti nello stesso inizializzatore! Ad esempio questo funziona:

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

Chiama i sovraccarichi corretti. Inoltre, cerca solo il metodo con nome Add, il tipo restituito potrebbe essere qualsiasi cosa.


0

La matrice come sintassi viene trasformata in una serie di Add()chiamate.

Per vedere questo in un esempio molto più interessante, considera il seguente codice in cui faccio due cose interessanti che sembrano prima illegali in C #, 1) impostazione di una proprietà di sola lettura, 2) impostazione di un elenco con un array come l'inizializzatore.

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"}
};

Questo codice funzionerà perfettamente, anche se 1) MyList è di sola lettura e 2) Ho impostato un elenco con inizializzatore di array.

Il motivo per cui funziona, è perché nel codice che fa parte di un intializzatore di oggetti il ​​compilatore trasforma sempre qualsiasi {}sintassi simile in una serie di Add()chiamate che sono perfettamente legali anche su un campo di sola lettura.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.