Как заставить встроенную инициализацию массива работать, например, инициализацию словаря?
Почему можно инициализировать Dictionary<T1,T2>
следующим образом:
var dict = new Dictionary<string,int>() {
{ "key1", 1 },
{ "key2", 2 }
};
...но не инициализировать, скажем, массив KeyValuePair<T1,T2>
объектов точно таким же образом:
var kvps = new KeyValuePair<string,int>[] {
{ "key1", 1 },
{ "key2", 2 }
};
// compiler error: "Array initializers can only be used in a variable
// or field initializer. Try using a new expression instead."
Я понимаю, что могу заставить работать второй пример, просто написав new KeyValuePair<string,int>() { "key1", 1 }
и т. д. Для каждого элемента. Но мне интересно, можно ли использовать тот же тип краткого синтаксиса, который возможен в первом примере.
Если это невозможно, то что же делает тип словаря таким особенным?
6 ответов:
Синтаксис инициализатора коллекции переводится в вызовы
Add
с соответствующим числом параметров:Этот специальный синтаксис инициализатора будет также работать на других классах, которые имеют методvar dict = new Dictionary<string,int>(); dict.Add("key1", 1); dict.Add("key2", 2);
Add
и реализуютIEnumerable
. Давайте создадим совершенно сумасшедший класс, чтобы доказать, что вDictionary
нет ничего особенного и что этот синтаксис может работать для любого подходящего класса:// Don't do this in production code! class CrazyAdd : IEnumerable { public void Add(int x, int y, int z) { Console.WriteLine(x + y + z); // Well it *does* add... } public IEnumerator GetEnumerator() { throw new NotImplementedException(); } }
Теперь вы можете написать следующее:
var crazyAdd = new CrazyAdd { {1, 2, 3}, {4, 5, 6} };
Выходы:
6 15
Смотрите работа в сети: ideone
Что касается других типов, о которых вы спрашивали:
- он не работает с массивом, потому что у него нет метода
Add
.List<T>
имеет методAdd
, но он имеет только один параметр.
Он работает со словарем, потому что у него есть перегрузка для
Add
, которая принимает два аргумента. Массивы даже не имеют методаAdd
, не говоря уже о методе с двумя аргументами.Класс
Dictionary
специально разработан для работы сKeyValuePair<,>
внутри, это единственная причина, по которой вам не нужно вызывать конструктор вручную, вместо этого вызывается двух аргументныйAdd
и создает KeyValuePair под капотом.Каждый другой
IEnumerable<KeyValuePair<,>>
не имеет этой специальной реализации и поэтому должен быть инициализирован следующим образом:var array = new KeyValuePair<int, int>[] { new KeyValuePair<int, int>(1, 2), new KeyValuePair<int, int>(3, 4) };
Вы можете создать такое же поведение со своими собственными классами, например, допустим, вы реализуете это:
class ThreeTupleList<T1, T2, T3> : List<Tuple<T1, T2, T3>> { public void Add(T1 a, T2 b, T3 c) { this.Add(new Tuple<T1, T2, T3>(a, b, c)); } // You can even implement a two-argument Add and mix initializers public void Add(T1 a, T2 b) { this.Add(new Tuple<T1, T2, T3>(a, b, default(T3))); } }
Вы можете инициализировать его так, и даже смешивать инициализаторы с тремя, двумя и одним аргументом:
var mylist = new ThreeTupleList<int, string, double>() { { 1, "foo", 2.3 }, { 4, "bar", 5.6 }, { 7, "no double here" }, null };
Ваша проблема связана с тем, что это массив, а не коллекция.
var kvps = new KeyValuePair<string,int>[] { { "key1", 1 }, { "key2", 2 } };
Действительно должно быть:
var kvps = new KeyValuePair<string, int>[] { new KeyValuePair<string, int>("key1", 1), new KeyValuePair<string, int>("key2", 2) };
Поддавка-это скобки.
[]
- это массив.{}
- это коллекция.
Спасибо нескольким ответчикам за указание на то, что метод Add является
волшебной вещьюсекретный соус , который заставляет работать синтаксис инициализации. Таким образом, я мог бы достичь своей цели, унаследовав рассматриваемый класс (KeyValuePair):public class InitializableKVPs<T1,T2> : IEnumerable<KeyValuePair<T1,T2>> { public void Add(T1 key, T2 value) { throw new NotImplementedException(); } public IEnumerator<KeyValuePair<string,string>> GetEnumerator() { throw new NotImplementedException(); } IEnumerator IEnumerable.GetEnumerator() { throw new NotImplementedException(); } }
Это теперь принимается компилятором:
var kvps = new InitializableKVPs<string,int> { { "key1", 1 }, { "key2", 2 } };
Edit : ответ Филиппа Добмайера имеет фактическую, краткую реализацию этого.
Переопределение IDictionary для синтаксиса C# 6.0
В случае, если кто-то придет сюда, как это сделал я, чтобы сохранить некоторые нажатия клавиш для нового синтаксиса инициализатора словаря C# 6.0, это можно сделать, но вместо этого требуется выводить из IDictionary. Вам нужно только реализовать метод this [] set, чтобы заставить его работать, что оставляет массу нереализованных методов.
Реализация класса выглядит следующим образом:
// Seriously! Don't do this in production code! Ever!!! public class CrazyAdd2 : IDictionary<string, int> { public int this[string key] { get { throw new NotImplementedException(); } set {Console.WriteLine($"([{key}]={value})"); } } #region NotImplemented // lots of empty methods go here #endregion }
Затем использовать его:
var crazyAdd2 = new CrazyAdd2 { ["one"] = 1, ["two"] = 2, };
И вывод:
([one]=1) ([two]=2)
И вот скрипка, демонстрирующая все это:
Вы можете не видеть его, но это тот же синтаксис. Единственное отличие состоит в том, что массив принимает элементы в качестве входных данных, в то время как словарь принимает 2-мерный массив.
int[] a = new int[]{5,6}; int[,] a = new int[]{{5,6},{3,6}}; Dictionary<int,int> a = new Dictionary<int,int>{{5,6},{3,6}};