Вопрос по magic-methods, initialization, syntax, c# – Как сделать так, чтобы инициализация встроенного массива работала, например, Словарь инициализации?

21

Почему можно инициализировать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 }и т. д. для каждого элемента. Но мне интересно, возможно ли использовать тот же тип краткого синтаксиса, который возможен в первом примере.

Если это невозможно, то что делаетDictionary типа такой особенный?

(Хотя я не думаю, что правильно понял название.) user166390
Мне нравится, где этот вопрос идет, но он кажется не определенным. Мне нравится идея сосредоточиться наList противDictionary (чтобы убрать массив), хотя сообщение об ошибке может отличаться? (Размышление: как узнать, как создать КВП из{a,b}? ;-) user166390
@ Да, я думаю, вы приближаетесь к сути того, о чем я спрашиваю. Я не очень хорошо пишу вопросы ... McGarnagle

Ваш Ответ

6   ответов
2

Спасибо нескольким ответчикам за указание на то, чтоAdd метод являетсяmagical thing secret sauce это заставляет синтаксис инициализации работать. Таким образом, я мог достичь своей цели, унаследовав соответствующий класс (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: Ответ Филиппа Добмайера имеет фактическую, краткую реализацию этого.

Только что узнал, что можно даже смешивать элементы инициализатора с разным количеством аргументов :) Обновил мой ответ еще раз ...
Именно так. Смотрите мой обновленный ответ для очень простой реализации этого, если вы просто наследуете от уже реализованногоICollection<>например,List<>
11

Он работает со словарем, потому что у него есть перегрузка для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
};
1
Override IDictionary for the C# 6.0 syntax

В случае, если кто-то придет сюда, как я, пытаясь сохранить некоторые нажатия клавиш для нового синтаксиса инициализатора словаря C # 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

33

Синтаксис инициализатора коллекции преобразуется в вызовы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

Что касается других типов, которые вы спрашивали о:

It doesn't work on an array because it has no Add method. List<T> has an Add method but it has only one parameter.
Смотрите обновление для объяснения.
задавался вопросом об этом навсегда, спасибо
Да, ноList does есть метод добавления, и попытка, которая выдает ту же ошибку. McGarnagle
Это очень зло Add (). Мне это нравится.
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)
};

Дешевая распродажа - скобки.[] это массив.{} это коллекция.

Возможно, мой вопрос не был четко выражен, но, пожалуйста, прочитайте его более внимательно. Я упомянул ваш пример в своем ответе. Я знаю, как написать инициализацию, но мне интересно узнать причину, по которой более простой синтаксис невозможен. McGarnagle
0

Вы можете не видеть это, но это тот же синтаксис. Единственное отличие состоит в том, что массив принимает элементы в качестве входных данных, а словарь принимает двумерный массив.

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}};
Это не совсем причина, почему этот синтаксический сахар возможен. На самом деле это связано с реализацией универсального словаря, который обеспечивает два аргументаAdd который строитKeyValuePair для тебя под капотом.

Похожие вопросы