Чому я можу ініціалізувати список як масив у C #?


131

Сьогодні я здивовано виявив, що в C # я вмію робити:

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

Чому я можу це зробити? Який конструктор називається? Як я можу це зробити на власних заняттях? Я знаю, що це спосіб ініціалізації масивів, але масиви - це мовні елементи, а списки - прості об'єкти ...


1
Це питання може бути корисним
Боб Кауфман

10
Досить приголомшливий так? Ви можете зробити дуже подібний код, щоб ініціалізувати словники:{ { "key1", "value1"}, { "key2", "value2"} }
danludwig

З іншого погляду цей масив, як синтаксис ініціалізації, нагадує, що базовий тип даних а 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 };
Joren

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достатньо, щоб дозволити використовувати синтаксис ініціалізатора колекції.
фог

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


8

Відповідно до специфікації C # версії 3.0 "Об'єкт колекції, до якого застосовано ініціалізатор колекції, повинен бути такого типу, який реалізує System.Collections.Generic.ICollection точно для однієї Т."

Однак ця інформація, як видається, є неточною; див. роз'яснення Еріка Ліпперта в коментарях нижче.


10
Ця сторінка не точна. Дякую за те, що ви звернули увагу на це. Це повинна бути попередня документація до випуску, яка якось ніколи не видалялася. Оригінальний дизайн повинен був вимагати ICollection, але це не остаточний дизайн, на якому ми зупинилися.
Ерік Ліпперт

1
Дякую за роз’яснення.
Олів'є Якот-Дескомб


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) встановлення властивості лише для читання; 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.