字典初始值设定项具有不同的行为,并在与数组初始值设定项组合使用时引发运行时异常

Nik*_*tov 9 c# dictionary initialization c#-6.0

我有以下C#代码,用int键和List<string>值初始化一个新的字典:

var dictionary =
    new Dictionary<int, List<string>>
    {
        [1] = new List<string> { "str1", "str2", "str3" },
        [2] = new List<string> { "str4", "str5", "str6" }
    };
Run Code Online (Sandbox Code Playgroud)

如果我将从此代码段生成的可执行文件反编译回C#,则相应的部分如下所示:

Dictionary<int, List<string>> expr_06 = new Dictionary<int, List<string>>();
expr_06[1] = new List<string>
{
    "str1",
    "str2",
    "str3"
};
expr_06[2] = new List<string>
{
    "str4",
    "str5",
    "str6"
};
Run Code Online (Sandbox Code Playgroud)

一切似乎正常,并在这里正常工作.

但是当我有以下代码时:

var dictionary2 =
    new Dictionary<int, List<string>>
    {
        [1] = { "str1", "str2", "str3" },
        [2] = { "str4", "str5", "str6" }
    };
Run Code Online (Sandbox Code Playgroud)

这看起来像普通代码并成功编译,但在运行时我得到以下异常:

System.Collections.Generic.KeyNotFoundException:'字典中没有给定的键.'

当我查看第二个示例的反编译代码时,我可以看到它与第一个示例不同:

Dictionary<int, List<string>> expr_6E = new Dictionary<int, List<string>>();
expr_6E[1].Add("str1");
expr_6E[1].Add("str2");
expr_6E[1].Add("str3");
expr_6E[2].Add("str4");
expr_6E[2].Add("str5");
expr_6E[2].Add("str6");
Run Code Online (Sandbox Code Playgroud)

当然,这解释了例外情况.

所以现在我的问题是:

  1. 这是预期的行为,是否记录在某处?

  2. 为什么允许上面的语法,但以下语法不是?

    List<string> list = { "test" };
    
    Run Code Online (Sandbox Code Playgroud)
  3. 为什么不允许以下语法?

    var dict = new Dictionary<int, string[]>
    {
        [1] = { "test1", "test2", "test3" },
        [2] = { "test4", "test5", "test6" }
    };
    
    Run Code Online (Sandbox Code Playgroud)

类似但不同的问题:

Adr*_*ian 5

让我试着回答你的所有问题:


  1. 这是预期的行为,是否记录在某处?

是的,它在C#6.0语言规范中的§7.6.11.2 对象初始化器和§7.6.11.3 集合初始化器中有介绍.

语法

var a =
    new Test
    {
        [1] = "foo"
        [2] = "bar"
    };
Run Code Online (Sandbox Code Playgroud)

实际上是在C#6.0中新引入的,作为索引器的先前对象初始化语法的扩展.与new(与对象创建表达式,§7.6.11)一起使用的对象初始值设定项总是转换为对象实例化和相应对象的成员访问(使用临时变量),在这种情况下:

var _a = new Test();
_a[1] = "foo";
_a[2] = "bar";
var a = _a;
Run Code Online (Sandbox Code Playgroud)

除了初始值设定项的每个元素作为参数传递给Add新创建的集合的方法之外,集合初始值设定项类似:

var list = new List<int> {1, 2};
Run Code Online (Sandbox Code Playgroud)

var _list = new List<int>();
_list.Add(1);
_list.Add(2);
var list = _list;
Run Code Online (Sandbox Code Playgroud)

对象初始值设定项还可以包含其他对象或集合初始值设定项.规范说明了集合初始化器的情况:

在等号后面指定集合初始值设定项的成员初始值设定项是嵌入式集合的初始化.不是将新集合分配给目标字段,属性或索引器,而是将初始化程序中给出的元素添加到目标引用的集合中.

因此,在对象初始值设定项中使用的唯一集合initalizer将不会尝试创建新的集合实例.它只会尝试将元素添加到现有集合中,即已在父对象的构造函数中实例化的集合.

写作

[1] = new List<string> { "str1", "str2", "str3" }
Run Code Online (Sandbox Code Playgroud)

实际上是一个完全不同的情况,因为这是一个对象创建表达式,它只包含一个集合初始值设定项,但不是一个.


  1. 为什么允许上面的语法,但以下语法不是?

    List<string> list = { "test" };
    
    Run Code Online (Sandbox Code Playgroud)

现在,这不再是集合初始化器了.集合initalizer只能出现在对象初始值设定项或对象创建表达式中.{ obj1, obj2 }作业旁边的唯一实际上是array initializer(§12.6).代码无法编译,因为您无法将数组分配给a List<string>.


  1. 为什么不允许以下语法?

    var dict = new Dictionary<int, string[]> 
    {
        [1] = { "test1", "test2", "test3" },
        [2] = { "test4", "test5", "test6" }     
    };
    
    Run Code Online (Sandbox Code Playgroud)

这是不允许的,因为集合初始化器只允许初始化集合,而不是数组类型(因为只有集合有一个Add方法).