【问题标题】:Dictionary initializer has different behavior and raises run-time exception when used in combination of array initializer字典初始值设定项具有不同的行为并在与数组初始值设定项组合使用时引发运行时异常
【发布时间】:2017-06-16 14:07:02
【问题描述】:

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

如果我将此 sn-p 生成的可执行文件反编译回 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: '给定的键不在字典中。'

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

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

类似但不同的问题:

【问题讨论】:

  • 使用new List&lt;string&gt; ,您将一个新对象分配给一个键。如果没有new,该语法用于初始化现有的集合对象。但是一个都没有。我很惊讶它可以编译。
  • @EdPlunkett 编译器无法知道您正在初始化一个不存在的值。
  • @Servy 好的,{ [1] = new List&lt;string&gt;(), [1] = { "foo", "bar", "baz" } } 很奇怪,但它可以工作——如果TValueList&lt;String&gt;。但事实证明,第二种情况无法编译,因为TValuestring[],它没有Add() 方法;我没有注意到第二个是一个数组。

标签: c# dictionary initialization c#-6.0


【解决方案1】:

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

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

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

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

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

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

【讨论】:

  • 您的文章中几乎没有拼写错误...仅供参考
【解决方案2】:

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


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

是的,它记录在 C# 6.0 语言规范中的第 7.6.11.2 节 对象初始化器 和 §7.6.11.3 集合初始化器 部分。

语法

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;

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

一个成员初始化器,它在 等号是嵌入式集合的初始化。代替 将新集合分配给目标字段、属性或索引器, 初始化器中给出的元素被添加到集合中 被目标引用。

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

写作

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

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


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

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

现在,这不再是集合初始化程序了。集合初始化器只能出现在对象初始化器或对象创建表达式中。作业旁边的唯一{ obj1, obj2 } 实际上是array initializer(第12.6 节)。该代码无法编译,因为您无法将数组分配给List&lt;string&gt;


  1. 那么为什么不允许下面的语法呢?

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

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

【讨论】:

  • 其实这个回答了我所有的问题。如果没有人提供更好的,我会接受。
猜你喜欢
  • 2011-05-22
  • 2012-09-06
  • 2014-01-23
  • 2014-04-10
  • 1970-01-01
  • 1970-01-01
  • 2020-09-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多