集合初始化程序语法之间的区别

Osk*_*erg 5 c# c#-6.0

MSDN声明:

通过使用集合初始值设定项,您不必在源代码中指定对类的Add方法的多个调用; 编译器添加调用.

他们也给出了这个例子,使用带括号的更新的集合初始化语法:

var numbers = new Dictionary<int, string> { 
    [7] = "seven", 
    [9] = "nine", 
    [13] = "thirteen" 
};
Run Code Online (Sandbox Code Playgroud)

但是,在检查生成的IL代码时,似乎这段代码根本不会导致对该Add方法的任何调用,而是对一个调用set_item,例如:

IL_0007: ldstr        "seven"
IL_000c: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<int32, string>::set_Item(!0/*int32*/, !1/*string*/)
Run Code Online (Sandbox Code Playgroud)

相反,带有大括号的"旧"语法给出以下内容:

// C# code:
var numbers2 = new Dictionary<Int32, String>
{
    {7, "seven"},
    {9, "nine"},
    {13, "thirteen"}
};

// IL code snippet:
// ----------
// IL_0033: ldstr        "seven"
// IL_0038: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<int32, string>::Add(!0/*int32*/, !1/*string*/)
Run Code Online (Sandbox Code Playgroud)

......正如您所看到Add的,正如预期的那样,调用结果就是结果.(人们只能假设上面提到的MSDN上的文本尚未更新.)

到目前为止,我发现了一个这种差异实际上很重要的案例,那就是古怪System.Collections.Specialized.NameValueCollection.这个允许一个键指向多个值.初始化可以通过两种方式完成:

const String key = "sameKey";
const String value1 = "value1";
const String value2 = "value2";

var collection1 = new NameValueCollection
{
    {key, value1},
    {key, value2}
};

var collection2 = new NameValueCollection
{
    [key] = value1,
    [key] = value2
};
Run Code Online (Sandbox Code Playgroud)

...但是由于前者实际调用的方式NameValueCollection::Add(string, string)存在差异,因此在查看每个集合的内容时结果会有所不同;

collection1 [key] ="value1,value2"

collection2 [key] ="value2"

我意识到旧语法和IEnumerable接口之间存在联系,以及编译器如何Add通过命名约定等来找到该方法.我也意识到任何索引器类型受新语法约束的好处,正如之前的SO回答中所讨论的那样.

从您的角度来看,也许这些都是预期的特征,但是我没有想到这些影响,我很想知道更多.

因此,我想知道在MSDN或其他地方是否有文档来源可以澄清语法选择带来的行为差异.我也想知道你是否知道任何其他例子,这个选择可能会产生如初始化时的影响NameValueCollection.

Cha*_*ger 5

我想为了最终的澄清,你必须去规范。C# 6 规范并未“正式”发布,但有一份非官方草案可用。

有趣的是,尽管它位于编程指南中,但索引器语法不是集合初始值设定项,而是对象初始值设定项。从7.6.11.3 'Collection Initializers' 开始

集合初始值设定项由一系列元素初始值设定项组成,由 { 和 } 标记括起来并用逗号分隔。每个元素初始值设定项指定一个要添加到正在初始化的集合对象中的元素,并由由 { 和 } 标记括起来并用逗号分隔的表达式列表组成。... 应用集合初始值设定项的集合对象必须是实现 System.Collections.IEnumerable 的类型,否则会发生编译时错误。对于按顺序指定的每个元素,集合初始值设定项调用目标对象上的 Add 方法,并将元素初始值设定项的表达式列表作为参数列表

7.6.11.2 'Object Intializers' 开始

对象初始值设定项由一系列成员初始值设定项组成,由 { 和 } 标记括起来并用逗号分隔。每个 member_initializer 指定一个初始化目标。标识符必须命名正在初始化的对象的可访问字段或属性,而方括号中的参数列表必须为正在初始化的对象上的可访问索引器指定参数

以此为例:

public class ItemWithIndexer
{
    private readonly Dictionary<string, string> _dictionary = 
        new Dictionary<string, string>();

    public string this[string index]
    {
        get { return _dictionary[index]; }
        set { _dictionary[index] = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,此类符合要求有一个集合初始化应用:它没有实现IEnumerable或有一个Add方法,所以任何试图以这种方式将导致编译时错误初始化。但是,这个针对索引器的对象初始值设定项将编译并工作(请参阅此小提琴):

var item = new ItemWithIndexer
{
    ["1"] = "value"
};
Run Code Online (Sandbox Code Playgroud)