Por que posso inicializar uma lista como uma matriz em c #?

131

Hoje fiquei surpreso ao descobrir que em C # eu posso fazer:

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

Por que eu posso fazer isso? Qual construtor é chamado? Como posso fazer isso com minhas próprias aulas? Eu sei que essa é a maneira de inicializar matrizes, mas matrizes são itens de idioma e Listas são objetos simples ...

Ignacio Soler Garcia
fonte
1
Esta pergunta pode ser útil
Bob Kaufman
10
Muito impressionante né? Você pode fazer um código muito semelhante para inicializar dicionários:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig
De outra visão, essa matriz, como a sintaxe de inicialização, nos lembra que o tipo de dados subjacente de a List<int>é na verdade apenas uma matriz. Eu odeio a equipe de C # pelo fato de não nomearem isso ArrayList<T>que soa tão óbvio e natural.
RBT 21/03

Respostas:

183

Isso faz parte da sintaxe do inicializador de coleção no .NET. Você pode usar esta sintaxe em qualquer coleção criada, desde que:

  • Implementa IEnumerable(preferencialmente IEnumerable<T>)

  • Tem um método chamado Add(...)

O que acontece é que o construtor padrão é chamado e, em seguida, Add(...)é chamado para cada membro do inicializador.

Assim, esses dois blocos são aproximadamente idênticos:

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;

Você pode chamar um construtor alternativo, se desejar, por exemplo, para evitar sobredimensionar o tamanho List<T>durante o crescimento, 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, }

Observe que o Add()método não precisa levar um único item; por exemplo, o Add()método Dictionary<TKey, TValue>leva dois itens:

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

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

Portanto, para adicionar isso à sua própria classe, tudo o que você precisa fazer, como mencionado, é implementar IEnumerable(novamente, de preferência IEnumerable<T>) e criar um ou mais 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();
    }
}

Então você pode usá-lo como as coleções BCL:

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

    // ...
}

(Para mais informações, consulte o MSDN )

James Michael Hare
fonte
29
Boa resposta, embora observe que não é exato dizer "exatamente idêntico" no seu primeiro exemplo. É exatamente idêntico ao List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; que é, a avariável não é inicializado até depois de toda a adiciona são chamados. Caso contrário, seria legal fazer algo como o List<int> a = new List<int>() { a.Count, a.Count, a.Count };que é uma coisa louca de se fazer.
Eric Lippert
1
@ user606723: Não há nenhuma diferença real entre List<int> a; a = new List<int>() { a.Count };eList<int> a = new List<int>() { a.Count };
Joren 13/01
4
@Joren: Você está correto; de fato, a especificação C # afirma que T x = y;é a mesma que T x; x = y;, Esse fato pode levar a algumas situações estranhas. Por exemplo, int x = M(out x) + x;é perfeitamente legal porque int x; x = M(out x) + x;é legal.
Eric Lippert
1
@JamesMichaelHare não é necessário implementar IEnumerable<T>; o não genérico IEnumerableé suficiente para permitir o uso da sintaxe do inicializador de coleção.
Phoog
2
O ponto de Eric é importante se você estiver usando algum tipo de coleção que implementa IDisposable. using (var x = new Something{ 1, 2 })não descartará o objeto se uma das Addchamadas falhar.
PORGES
11

É o chamado açúcar sintático .

List<T> é a classe "simples", mas o compilador oferece um tratamento especial para facilitar sua vida.

Esse é o chamado inicializador de coleção . Você precisa implementar IEnumerable<T>e Addmétodo.

Krizz
fonte
8

De acordo com a especificação do C # versão 3.0 "O objeto de coleção ao qual um inicializador de coleção é aplicado deve ser de um tipo que implemente System.Collections.Generic.ICollection por exatamente um T."

No entanto, essas informações parecem imprecisas até o momento da redação deste documento; veja o esclarecimento de Eric Lippert nos comentários abaixo.

Olivier Jacot-Descombes
fonte
10
Essa página não é precisa. Obrigado por trazer isso à minha atenção. Essa deve ser uma documentação preliminar de pré-lançamento que de alguma forma nunca foi excluída. O design original deveria exigir o ICollection, mas esse não é o design final em que decidimos.
Eric Lippert
1
Obrigado pelo esclarecimento.
Olivier Jacot-Descombes
6

Ele funciona graças aos inicializadores de coleção que basicamente exigem que a coleção implemente um método Add e que fará o trabalho por você.

vc 74
fonte
6

Outra coisa interessante sobre os inicializadores de coleção é que você pode ter várias sobrecargas de Addmétodo e chamá-las todas no mesmo inicializador! Por exemplo, isso 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
};

Chama as sobrecargas corretas. Além disso, procura apenas o método com nome Add, o tipo de retorno pode ser qualquer coisa.

nawfal
fonte
0

A matriz como sintaxe está sendo transformada em uma série de Add() chamadas.

Para ver isso em um exemplo muito mais interessante, considere o código a seguir, no qual eu faço duas coisas interessantes que parecem ilegais em C #, 1) configurando uma propriedade somente leitura, 2) configurando uma lista com uma 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"}
};

Esse código funcionará perfeitamente, embora 1) MyList seja somente leitura e 2) eu defina uma lista com o inicializador de array.

A razão pela qual isso funciona é porque, no código que faz parte de um inicializador de objetos, o compilador sempre transforma qualquer {}sintaxe semelhante em uma série de Add()chamadas que são perfeitamente legais, mesmo em um campo somente leitura.

yoel halb
fonte