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
时产生影响。
答案 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"
};