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类型如此特别?

4

6 回答 6

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

至于您询问的其他类型:

  • 它不适用于数组,因为它没有Add方法。
  • List<T>有一个Add方法,但它只有一个参数。
于 2012-06-24T01:47:16.080 回答
11

它确实适用于 Dictionary,因为它有一个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
};
于 2012-06-24T01:49:01.693 回答
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)
};

赠品是括号。[]是一个数组。{}是一个集合。

于 2012-06-24T01:43:00.777 回答
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 }
};

编辑:Philip Daubmeier 的回答对此有一个实际、简洁的实现。

于 2012-06-24T02:09:39.150 回答
1

覆盖 C# 6.0 语法的 IDictionary

如果有人像我一样来到这里,希望为新的 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

于 2017-02-06T15:14:35.730 回答
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}};
于 2012-06-24T01:50:04.927 回答