List <int> test = {1,2,3} – 是一个特性还是一个bug?

如您所知,不允许在列表中使用Array-initialisation语法。 它会给编译时错误。 例:

List<int> test = { 1, 2, 3} // At compilation the following error is shown: // Can only use array initializer expressions to assign to array types. 

但是今天我做了以下(非常简化):

 class Test { public List<int> Field; } List<Test> list = new List<Test> { new Test { Field = { 1, 2, 3 } } }; 

上面的代码编译得很好,但运行时会给出“对象引用未设置为对象”的运行时错误。

我希望该代码给编译时错误。 我对你的问题是:为什么不这样做,并且有什么好的理由来解决这种情况?

这已经使用.NET 3.5,.Net和Mono编译器进行了testing。

干杯。

我认为这是一个devise行为。 Test = { 1, 2, 3 }被编译成调用存储在Test字段中的列表的Add方法的代码。

你得到NullReferenceException的原因是Testnull 。 如果您将Test字段初始化为新列表,则代码将工作:

 class Test { public List<int> Field = new List<int>(); } // Calls 'Add' method three times to add items to 'Field' list var t = new Test { Field = { 1, 2, 3 } }; 

这很合乎逻辑 – 如果你编写new List<int> { ... }那么它将创build一个新的list实例。 如果你不添加对象构造,它将使用现有的实例(或null )。 据我所知,C#规范并没有包含任何符合这个场景的显式的转换规则,但是它给出了一个例子(见第7.6.10.3节 ):

List<Contact>可以创build和初始化如下:

 var contacts = new List<Contact> { new Contact { Name = "Chris Smith", PhoneNumbers = { "206-555-0101", "425-882-8080" } }, new Contact { Name = "Bob Harris", PhoneNumbers = { "650-555-0199" } } }; 

其效果与之相同

 var contacts = new List<Contact>(); Contact __c1 = new Contact(); __c1.Name = "Chris Smith"; __c1.PhoneNumbers.Add("206-555-0101"); __c1.PhoneNumbers.Add("425-882-8080"); contacts.Add(__c1); Contact __c2 = new Contact(); __c2.Name = "Bob Harris"; __c2.PhoneNumbers.Add("650-555-0199"); contacts.Add(__c2); 

其中__c1__c2是临时variables,否则不可见且无法访问。

我希望该代码给编译时错误。

既然你的期望是违背规范和实施,你的期望将会无法实现。

为什么在编译时没有失败呢?

因为规范在第7.6.10.2节特别指出这是合法的,为了方便您在这里引用它:


在等号后指定集合初始值设定项的成员初始值设定项是embedded式集合的初始化。 初始化程序中给出的元素将被添加到字段或属性所引用的集合中,而不是将新的集合分配给字段或属性。


这种代码什么时候能正确运行?

正如规范所说,初始化器中给出的元素被添加到属性所引用的集合中。 该物业不参考收集; 它是空的。 因此在运行时它给出一个空引用exception。 有人必须初始化列表。 我会build议更改“testing”类,以便其构造函数初始化列表。

什么情景激发这个function?

LINQ查询需要expression式,而不是语句。 将成员添加到新创build的列表中的新build集合需要调用“添加”。 由于“Add”是无效返回的,所以对它的调用只能出现在expression式语句中。 这个特性允许你创build一个新的集合(使用“new”)并填充它,或者填充一个现有的集合(没有“new”),集合是你创build的一个对象的成员,这是LINQ查询。

此代码:

 Test t = new Test { Field = { 1, 2, 3 } }; 

被翻译成这个:

 Test t = new Test(); t.Field.Add(1); t.Field.Add(2); t.Field.Add(3); 

由于Fieldnull ,因此您将得到NullReferenceException

这被称为集合初始化器 ,如果你这样做,它将在你的第一个例子中工作:

 List<int> test = new List<int> { 1, 2, 3 }; 

为了能够使用这个语法,你真的需要新增一些东西,也就是说,一个集合初始值设定项只能出现在一个对象创buildexpression式的上下文中。 在C#规范的第7.6.10.1节中,这是对象创buildexpression式的语法:

 object-creation-expression: new type ( argument-list? ) object-or-collection-initializer? new type object-or-collection-initializer object-or-collection-initializer: object-initializer collection-initializer 

所以这一切都是从一个newexpression开始。 在expression式里面,你可以使用一个没有new的集合初始值设定项(见第7.6.10.2节):

 object-initializer: { member-initializer-list? } { member-initializer-list , } member-initializer-list: member-initializer member-initializer-list , member-initializer member-initializer: identifier = initializer-value initializer-value: expression object-or-collection-initializer // here it recurses 

现在,你真正缺less的是一些列表文字,这将是非常方便的。 我在这里提出了一个这样的字面值的枚举。

 var test = (new [] { 1, 2, 3}).ToList(); 

原因是第二个例子是一个成员列表初始化程序 – 而System.Linq.Expressions中的MemberListBindingexpression式给出了一个洞察 – 请参阅我对这个问题的更多答案的回答: 什么是MemberBinding的一些例子LINQ表情?

这种types的初始化程序要求列表已经初始化,以便您提供的序列可以添加到它。

因此,从语法NullReferenceException ,代码绝对没有错 – NullReferenceException是一个运行时错误,而这个错误并没有被创build。 一个默认的构造函数是new的列表,或者是代码体中的new内联,将解决运行时错误。

至于为什么这和代码的第一行有区别 – 在你的例子中是不允许的,因为这种types的expression式不能在赋值的右边,因为实际上并没有创build任何东西,它只是速记为Add

将您的代码更改为:

 class Test { public List<int> Field = new List<int>(); } 

原因是你必须明确地创build一个集合对象,然后才能将项目放入它。