Почему я могу инициализировать List как массив в С#?
Сегодня я с удивлением обнаружил, что в С# я могу сделать:
List<int> a = new List<int> { 1, 2, 3 };
Почему я могу это сделать? Какой конструктор называется? Как я могу сделать это с помощью своих собственных классов? Я знаю, что это способ инициализации массивов, но массивы - это языковые элементы, а списки - простые объекты...
Ответы
Ответ 1
Это часть синтаксиса инициализатора коллекции в .NET. Вы можете использовать этот синтаксис для любой создаваемой вами коллекции, если:
Что происходит, когда вызывается конструктор по умолчанию, а затем для каждого члена инициализатора вызывается 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.