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
的原因是Test
是null
。 如果您将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);
由于Field
为null
,因此您将得到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
所以这一切都是从一个new
expression开始。 在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一个集合对象,然后才能将项目放入它。