Как заставить встроенную инициализацию массива работать, например, инициализацию словаря?


Почему можно инициализировать 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 20

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)

И вот скрипка, демонстрирующая все это:

Https://dotnetfiddle.net/Lovy7m

Вы можете не видеть его, но это тот же синтаксис. Единственное отличие состоит в том, что массив принимает элементы в качестве входных данных, в то время как словарь принимает 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}};