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

时间:2016-08-23 13:23:05

标签: c# c#-6.0

MSDN声明:

  

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

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

var numbers = new Dictionary<int, string> { 
    [7] = "seven", 
    [9] = "nine", 
    [13] = "thirteen" 
};

但是,在检查生成的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*/)

&#34; old&#34;相反,带有大括号的语法提供以下内容:

// 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*/)

...正如您所看到的,正如预期的那样,对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
};

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

  

collection1 [key] =&#34; value1,value2&#34;

     

collection2 [key] =&#34; value2&#34;

我意识到旧语法和IEnumerable接口之间存在连接,以及编译器如何通过命名约定等来找到Add方法。我也意识到任何索引器类型受新语法约束的好处,如this SO answer之前所述。

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

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

1 个答案:

答案 0 :(得分:4)

我想最终澄清一下,你必须遵守规范。 C#6规范不是正式的#39;已发布,但有unofficial draft可用。

这里有趣的是,尽管它位于编程指南中,索引器语法是一个集合初始化器,它是一个对象初始化器。来自7.6.11.3 'Collection Initializers'

  

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

来自7.6.11.2 'Object Intializers'

  

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

以此为例:

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

请注意,此类符合应用集合初始值设定项的要求:它不实现IEnumerable或具有Add方法,因此任何初始化尝试以这种方式会导致编译时错误。这个针对索引器的对象初始化程序将编译并运行(参见this fiddle):

var item = new ItemWithIndexer
{
    ["1"] = "value"
};