¿Por qué puedo inicializar una lista como una matriz en C #?

131

Hoy me sorprendió descubrir que en C # puedo hacer:

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

¿Por qué puedo hacer esto? ¿Qué constructor se llama? ¿Cómo puedo hacer esto con mis propias clases? Sé que esta es la forma de inicializar matrices, pero las matrices son elementos de lenguaje y las listas son objetos simples ...

Ignacio Soler García
fuente
1
Esta pregunta puede ser de ayuda: stackoverflow.com/questions/1744967/…
Bob Kaufman
10
Bastante impresionante, ¿eh? También puede hacer un código muy similar para inicializar diccionarios:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig
Desde otra vista, esta matriz, como la sintaxis de inicialización, nos recuerda que el tipo de datos subyacente de a List<int>es en realidad solo una matriz. Odio el equipo de C # por el hecho de que no lo nombran, lo ArrayList<T>que suena tan obvio y natural.
RBT

Respuestas:

183

Esto es parte de la sintaxis del inicializador de colección en .NET. Puede usar esta sintaxis en cualquier colección que cree siempre que:

  • Implementa IEnumerable(preferiblemente IEnumerable<T>)

  • Tiene un método llamado Add(...)

Lo que sucede es que se llama al constructor predeterminado, y luego Add(...)se llama para cada miembro del inicializador.

Por lo tanto, estos dos bloques son más o menos idénticos:

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

Y

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

Usted puede llamar a un constructor alternativo si lo desea, por ejemplo, para evitar el exceso de dimensionar el List<T>durante el cultivo, 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, }

Tenga en cuenta que el Add()método no necesita tomar un solo elemento, por ejemplo, el Add()método Dictionary<TKey, TValue>toma dos elementos:

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

Es aproximadamente idéntico a:

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

Entonces, para agregar esto a su propia clase, todo lo que necesita hacer, como se mencionó, es implementar IEnumerable(nuevamente, preferiblemente IEnumerable<T>) y crear uno o más Add()métodos:

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

Luego puede usarlo como lo hacen las colecciones BCL:

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

    // ...
}

(Para más información, vea el MSDN )

James Michael Hare
fuente
29
Buena respuesta, aunque observo que no es del todo exacto decir "exactamente idéntico" en su primer ejemplo. Es exactamente idéntica a List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; Es decir, la avariable no se inicializa hasta que después de toda la suma que se llama. De lo contrario, sería legal hacer algo como lo List<int> a = new List<int>() { a.Count, a.Count, a.Count };que es una locura.
Eric Lippert el
1
@ user606723: No hay ninguna diferencia real entre List<int> a; a = new List<int>() { a.Count };yList<int> a = new List<int>() { a.Count };
Joren
44
@Joren: Tienes razón; de hecho, la especificación de C # establece que T x = y;es lo mismo que T x; x = y;, Este hecho puede conducir a algunas situaciones extrañas. Por ejemplo, int x = M(out x) + x;es perfectamente legal porque int x; x = M(out x) + x;es legal.
Eric Lippert el
1
@JamesMichaelHare no es necesario implementar IEnumerable<T>; el no genérico IEnumerablees suficiente para permitir el uso de la sintaxis de inicializador de colección.
phoog
2
El punto de Eric es importante si está utilizando algún tipo de colección que implemente IDisposable. using (var x = new Something{ 1, 2 })no eliminará el objeto si Addfalla una de las llamadas.
porges
8

De acuerdo con la especificación de C # Versión 3.0 "El objeto de colección al que se aplica un inicializador de colección debe ser de un tipo que implemente System.Collections.Generic.ICollection para exactamente una T."

Sin embargo, esta información parece ser inexacta al momento de escribir esto; vea la aclaración de Eric Lippert en los comentarios a continuación.

Olivier Jacot-Descombes
fuente
10
Esa página no es precisa. Gracias por llamar mi atención sobre esto. Esa debe ser una documentación preliminar previa al lanzamiento que de alguna manera nunca se eliminó. El diseño original requería ICollection, pero ese no es el diseño final en el que nos decidimos.
Eric Lippert el
1
Gracias por la aclaración.
Olivier Jacot-Descombes
6

¡Otra cosa interesante sobre los inicializadores de colección es que puede tener múltiples sobrecargas de Addmétodo y puede llamarlos a todos en el mismo inicializador! Por ejemplo, esto funciona:

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

Llama a las sobrecargas correctas. Además, busca solo el método con el nombre Add, el tipo de retorno podría ser cualquier cosa.

nawfal
fuente
0

La sintaxis tipo matriz se está convirtiendo en una serie de Add()llamadas.

Para ver esto en un ejemplo mucho más interesante, considere el siguiente código en el que hago dos cosas interesantes que suenan primero ilegales en C #, 1) establecer una propiedad de solo lectura, 2) establecer una lista con una matriz como inicializador.

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

Este código funcionará perfectamente, aunque 1) MyList es de solo lectura y 2) configuro una lista con inicializador de matriz.

La razón por la que esto funciona es porque en el código que forma parte de un intializador de objetos, el compilador siempre convierte cualquier {}sintaxis similar en una serie de Add()llamadas que son perfectamente legales incluso en un campo de solo lectura.

yoel halb
fuente