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


131

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

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

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


1
Этот вопрос может помочь: stackoverflow.com/questions/1744967/…
Боб Кауфман

10
Довольно круто, да? Вы можете использовать очень похожий код для инициализации словарей:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig

С другой стороны, этот массив, такой как синтаксис инициализации, напоминает нам о том, что базовый тип данных a List<int>на самом деле является только массивом. Ненавижу команду C # за то, что они не называют это ArrayList<T>так очевидным и естественным.
RBT

Ответы:


183

Это часть синтаксиса инициализатора коллекции в .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()'s 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 )


29
Хороший ответ, хотя я отмечаю, что в вашем первом примере было бы не совсем точно сказать «точно идентичный». Это точно совпадает с List<int> temp = new List<int>(); temp.Add(1); ... List<int> a = temp; То есть, aпеременная не инициализирована , пока после того, как все добавляет называются. В противном случае было бы законным делать что-то вроде List<int> a = new List<int>() { a.Count, a.Count, a.Count };безумия.
Эрик Липперт,

1
@ user606723: Нет никакой реальной разницы между List<int> a; a = new List<int>() { a.Count };иList<int> a = new List<int>() { a.Count };
Джорен

4
@Joren: Вы правы; на самом деле спецификация C # утверждает, что T x = y;это то же самое, что и T x; x = y;, Этот факт может привести к некоторым странным ситуациям. Например, int x = M(out x) + x;это совершенно законно, потому что int x; x = M(out x) + x;это законно.
Эрик Липперт,

1
@JamesMichaelHare реализовывать не обязательно IEnumerable<T>; неуниверсального IEnumerableдостаточно, чтобы разрешить использование синтаксиса инициализатора коллекции.
phoog

2
Замечание Эрика важно, если вы используете какую-то коллекцию, реализующую IDisposable. using (var x = new Something{ 1, 2 })не удалит объект, если один из Addвызовов не удастся.
porges


8

Согласно спецификации C # версии 3.0 «Объект коллекции, к которому применяется инициализатор коллекции, должен иметь тип, реализующий System.Collections.Generic.ICollection ровно для одного T.»

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


10
Эта страница не точна. Спасибо, что обратили на это мое внимание. Это должна быть какая-то предварительная предварительная документация, которую почему-то так и не удалили. Первоначальный дизайн требовал использования ICollection, но это не окончательный вариант, на котором мы остановились.
Эрик Липперт,

1
Спасибо за разъяснение.
Olivier Jacot-Descombes


6

Еще одна интересная особенность инициализаторов коллекций заключается в том, что у вас может быть несколько перегрузок 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, возвращаемый тип может быть любым.


0

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

Чтобы увидеть это на гораздо более интересном примере, рассмотрим следующий код, в котором я делаю две интересные вещи, которые сначала кажутся незаконными в C #: 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) я установил список с инициализатором массива.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.