Как сделать инициализацию встроенного массива, например, Инициализация словаря?
Почему возможно инициализировать 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 }
и т.д. для каждого элемента. Но мне интересно, можно ли использовать тот же тип сжатого синтаксиса, который возможен в первом примере.
Если это невозможно, то что делает тип словаря таким особенным?
Ответы
Ответ 1
Синтаксис инициализатора коллекции преобразуется в вызовы 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
, но имеет только один параметр.
Ответ 2
Он работает с Словарем, потому что он имеет перегруз для 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
};
Ответ 3
Ваша проблема связана с тем, что это массив, а не коллекция.
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)
};
Под рукой есть скобки. []
- массив. {}
- это коллекция.
Ответ 4
Благодаря нескольким респондентам, указав, что метод Add - это секретный соус magical thing, который делает синтаксис инициализации. Поэтому я мог бы достичь своей цели, наследуя рассматриваемый класс (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 }
};
Изменить: ответ Филиппа Дабмайера содержит фактическую и краткую реализацию этого.
Ответ 5
Переопределить IDictionary для синтаксиса С# 6.0
В случае, если кто-то приходит сюда, как и я, желая сохранить несколько нажатий клавиш для нового синтаксиса инициализатора словаря С# 6.0, это можно сделать, но вместо этого требуется получение из IDictionary. Вам нужно всего лишь реализовать этот метод [] 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)
И вот скрипка, демонстрирующая все это:
https://dotnetfiddle.net/Lovy7m
Ответ 6
Возможно, вы его не видите, но это тот же синтаксис. Единственное отличие состоит в том, что массив принимает элементы в качестве ввода, где словарь принимает 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}};