Pourquoi puis-je initialiser une liste comme un tableau en C #?

131

Aujourd'hui, j'ai été surpris de constater qu'en C # je peux faire:

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

Pourquoi puis-je faire ça? Quel constructeur s'appelle? Comment puis-je faire cela avec mes propres cours? Je sais que c'est la manière d'initialiser les tableaux, mais les tableaux sont des éléments de langage et les listes sont des objets simples ...

Ignacio Soler Garcia
la source
1
Cette question peut être utile: stackoverflow.com/questions/1744967/…
Bob Kaufman
10
Assez génial hein? Vous pouvez également faire du code très similaire pour initialiser les dictionnaires:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig
D'un autre point de vue, ce tableau, comme la syntaxe d'initialisation, nous rappelle que le type de données sous-jacent de a List<int>est en fait un tableau uniquement. Je déteste l'équipe C # pour le fait qu'ils ne la nomment pas, ce ArrayList<T>qui semble si évident et naturel.
RBT

Réponses:

183

Cela fait partie de la syntaxe d'initialisation de collection dans .NET. Vous pouvez utiliser cette syntaxe sur n'importe quelle collection que vous créez tant que:

  • Il met en œuvre IEnumerable(de préférence IEnumerable<T>)

  • Il a une méthode nommée Add(...)

Ce qui se passe, c'est que le constructeur par défaut est appelé, puis Add(...)est appelé pour chaque membre de l'initialiseur.

Ainsi, ces deux blocs sont à peu près identiques:

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

Et

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

Vous pouvez appeler un constructeur alternatif si vous le souhaitez, par exemple pour éviter de surdimensionner le List<T>pendant la croissance, etc.:

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

Notez que la Add()méthode n'a pas besoin de prendre un seul élément, par exemple la Add()méthode pour Dictionary<TKey, TValue>prend deux éléments:

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

Est à peu près identique à:

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

Donc, pour ajouter ceci à votre propre classe, tout ce que vous avez à faire, comme mentionné, est d'implémenter IEnumerable(encore une fois, de préférence IEnumerable<T>) et de créer une ou plusieurs Add()méthodes:

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

Ensuite, vous pouvez l'utiliser comme le font les collections BCL:

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

    // ...
}

(Pour plus d'informations, consultez le MSDN )

James Michael Hare
la source
29
Bonne réponse, même si je note qu'il n'est pas tout à fait exact de dire «exactement identique» dans votre premier exemple. C'est exactement identique à List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; Autrement dit, la avariable n'est initialisée qu'après l'appel de tous les ajouts. Sinon, il serait légal de faire quelque chose comme List<int> a = new List<int>() { a.Count, a.Count, a.Count };ce qui est fou.
Eric Lippert
1
@ user606723: Il n'y a pas de vraie différence entre List<int> a; a = new List<int>() { a.Count };etList<int> a = new List<int>() { a.Count };
Joren
4
@Joren: Vous avez raison; en fait, la spécification C # indique que T x = y;c'est la même chose que T x; x = y;, Ce fait peut conduire à des situations étranges. Par exemple, int x = M(out x) + x;c'est parfaitement légal parce que int x; x = M(out x) + x;c'est légal.
Eric Lippert
1
@JamesMichaelHare il n'est pas nécessaire de mettre en œuvre IEnumerable<T>; le non-générique IEnumerablesuffit pour permettre l'utilisation de la syntaxe d'initialisation de collection.
phoog
2
Le point d'Eric est important si vous utilisez une sorte de collection qui implémente IDisposable. using (var x = new Something{ 1, 2 })ne supprimera pas l'objet si l'un des Addappels échoue.
porges
11

C'est ce qu'on appelle le sucre syntaxique .

List<T> est la classe "simple", mais le compilateur lui donne un traitement spécial afin de vous faciliter la vie.

Celui-ci est appelé initialiseur de collection . Vous devez mettre en œuvre IEnumerable<T>et Addméthode.

Krizz
la source
8

Selon la spécification C # version 3.0 "L'objet de collection auquel un initialiseur de collection est appliqué doit être d'un type qui implémente System.Collections.Generic.ICollection pour exactement un T."

Cependant, ces informations semblent inexactes au moment de la rédaction de cet article; voir la clarification d'Eric Lippert dans les commentaires ci-dessous.

Olivier Jacot-Descombes
la source
10
Cette page n'est pas exacte. Merci d'avoir porté ceci à mon attention. Cela doit être une documentation préliminaire préliminaire qui n'a jamais été supprimée. La conception originale nécessitait ICollection, mais ce n'est pas la conception finale sur laquelle nous nous sommes arrêtés.
Eric Lippert
1
Merci pour la clarification.
Olivier Jacot-Descombes
6

Cela fonctionne grâce aux initialiseurs de collection qui nécessitent essentiellement que la collection implémente une méthode Add et qui fera le travail pour vous.

vc 74
la source
6

Une autre chose intéressante à propos des initialiseurs de collection est que vous pouvez avoir plusieurs surcharges de Addméthode et vous pouvez les appeler toutes dans le même initialiseur! Par exemple, cela fonctionne:

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

Il appelle les surcharges correctes. En outre, il ne recherche que la méthode avec le nom Add, le type de retour peut être n'importe quoi.

nawfal
la source
0

Le tableau comme la syntaxe est tourné dans une série d' Add()appels.

Pour voir cela dans un exemple beaucoup plus intéressant, considérons le code suivant dans lequel je fais deux choses intéressantes qui semblent d'abord illégales en C #, 1) définir une propriété en lecture seule, 2) définir une liste avec un tableau comme initializer.

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

Ce code fonctionnera parfaitement, même si 1) MyList est en lecture seule et 2) j'ai défini une liste avec un initialiseur de tableau.

La raison pour laquelle cela fonctionne, c'est que dans le code qui fait partie d'un initiateur d'objet, le compilateur transforme toujours une {}syntaxe similaire en une série d' Add()appels qui sont parfaitement légaux même sur un champ en lecture seule.

yoel halb
la source