当与数组初始化器组合使用时,字典初始化器具有不同的行为并引发 运行 次异常

Dictionary initializer has different behavior and raises run-time exception when used in combination of array initializer

我有以下 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" }
    };

如果我将从此片段生成的可执行文件反编译回 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"
};

这里一切正常,工作正常。

但是当我有以下代码时:

var dictionary2 =
    new Dictionary<int, List<string>>
    {
        [1] = { "str1", "str2", "str3" },
        [2] = { "str4", "str5", "str6" }
    };

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

System.Collections.Generic.KeyNotFoundException: 'The given key was not present in the dictionary.'

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

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");

当然这也解释了例外情况。

所以现在我的问题是:

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

  2. 为什么上面的语法允许而下面的语法不允许?

    List<string> list = { "test" };
    
  3. 为什么不允许使用以下语法?

    var dict = new Dictionary<int, string[]>
    {
        [1] = { "test1", "test2", "test3" },
        [2] = { "test4", "test5", "test6" }
    };
    

类似但不同的问题:

使用列表初始化器(即使用 = { "test1", "test2", "test3" })假定列表已经以某种方式初始化,例如在 class´ 构造函数中是这样的:

class MyClass
{
    List<string> TheList = new List<string>();
}

现在您可以使用列表初始化器了:

var m = new MyClass { TheList = { myElementsHere } };

正如您已经展示的那样,这只是调用 Add 方法的快捷方式。

但是在您的情况下,列表根本没有初始化,因此调用列表初始化程序会引发上述异常。仅仅声明一个 Dictionary 其值是任何类型的列表并不意味着你已经初始化了这些列表。在调用它的任何方法之前,你必须像对待任何其他 class 一样执行此操作(例如 Add 只是包装在初始化程序中)。

让我尝试回答您的所有问题:


  1. Is this expected behavior and is it documented somewhere?

是的,它记录在 C# 6.0 语言规范的第 §7.6.11.2 对象初始值设定项 和第 7.6.11.3 节集合初始值设定项[=60= 下].

语法

var a =
    new Test
    {
        [1] = "foo"
        [2] = "bar"
    };

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

var _a = new Test();
_a[1] = "foo";
_a[2] = "bar";
var a = _a;

除了初始化器的每个元素都作为参数传递给新创建的集合的 Add 方法之外,集合初始化器与此类似:

var list = new List<int> {1, 2};

变成

var _list = new List<int>();
_list.Add(1);
_list.Add(2);
var list = _list;

对象初始值设定项还可以包含其他对象或集合初始值设定项。集合初始值设定项的规范说明:

A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the target field, property or indexer, the elements given in the initializer are added to the collection referenced by the target.

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

写作

[1] = new List<string> { "str1", "str2", "str3" }

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


  1. Why is the syntax above allowed but the following syntax is not?

    List<string> list = { "test" };
    

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


  1. Why is the following syntax not allowed then?

    var dict = new Dictionary<int, string[]> 
    {
        [1] = { "test1", "test2", "test3" },
        [2] = { "test4", "test5", "test6" }     
    };
    

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