Почему я могу инициализировать List как массив в С#?

Сегодня я с удивлением обнаружил, что в С# я могу сделать:

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

Почему я могу это сделать? Какой конструктор называется? Как я могу сделать это с помощью своих собственных классов? Я знаю, что это способ инициализации массивов, но массивы - это языковые элементы, а списки - простые объекты...

Ответ 1

Это часть синтаксиса инициализатора коллекции в .NET. Вы можете использовать этот синтаксис для любой создаваемой вами коллекции, если:

  • Он реализует IEnumerable (желательно IEnumerable<T>)

  • Он имеет метод с именем Add(...)

Что происходит, когда вызывается конструктор по умолчанию, а затем для каждого члена инициализатора вызывается Add(...).

Таким образом, эти два блока примерно одинаковы:

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

и

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

Вы можете вызвать альтернативный конструктор, если хотите, например, для предотвращения переопределения List<T> во время роста и т.д.:

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

Обратите внимание, что для метода Add() не нужно брать один элемент, например, метод Add() для Dictionary<TKey, TValue> принимает два элемента:

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

Примерно совпадает с:

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

Итак, чтобы добавить это в свой собственный класс, все, что вам нужно сделать, как уже упоминалось, это реализовать IEnumerable (опять же, желательно IEnumerable<T>) и создать один или несколько методов Add():

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

Затем вы можете использовать его так же, как коллекции BCL:

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

    // ...
}

(Для получения дополнительной информации см. MSDN)

Ответ 2

Это так называемый синтаксический сахар.

List<T> - это "простой" класс, но компилятор придает ему особое отношение, чтобы облегчить вашу жизнь.

Этот так называемый инициализатор коллекции. Вам необходимо реализовать методы IEnumerable<T> и Add.

Ответ 3

В соответствии с С# Version 3.0 Specification "Объект коллекции, к которому применяется инициализатор коллекции, должен иметь тип, который реализует System.Collections.Generic.ICollection для ровно одного T."

Однако эта информация представляется неточной на момент написания этой статьи; см. пояснения Эрика Липперта в комментариях ниже.

Ответ 4

Он работает благодаря инициализаторам коллекций, которые в основном требуют, чтобы сборник реализовал метод Add, и который будет работать для вас.

Ответ 5

Еще одна интересная вещь о инициализаторах коллекции заключается в том, что вы можете иметь несколько перегрузок метода Add, и вы можете вызывать их всех в одном и том же инициализаторе! Например, это работает:

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

Он вызывает правильные перегрузки. Кроме того, он ищет только метод с именем Add, тип возвращаемого значения может быть любым.

Ответ 6

Массив, подобный синтаксису, преобразуется в ряд вызовов Add().

Чтобы увидеть это в гораздо более интересном примере, рассмотрим следующий код, в котором я делаю две интересные вещи, которые сначала звучат недопустимо в С#, 1) устанавливают свойство readonly, 2) устанавливают список с таким массивом, как инициализатор.

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

Этот код будет работать отлично, хотя 1) MyList - только для чтения и 2) Я установил список с инициализатором массива.

Причина, по которой это работает, заключается в том, что в коде, который является частью объекта intializer, компилятор всегда превращает любой синтаксис {} в серию вызовов Add(), которые совершенно законны даже в поле readonly.