如您所知,不允许将Array-initialisation语法与Lists一起使用。它会产生编译时错误。例如:
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编译器。
干杯。
答案 0 :(得分:47)
我认为这是一种设计行为。 Test = { 1, 2, 3 }
被编译为代码,该代码调用Add
字段中存储的列表的Test
方法。
您获得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> { ... }
,那么它会创建一个新的列表实例。如果不添加对象构造,它将使用现有实例(或null
)。据我所知,C#规范不包含任何符合此场景的显式转换规则,但它给出了一个示例(参见第7.6.10.3节):
可以按如下方式创建和初始化List<Contact>
:
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
是临时变量,否则将无形且无法访问。
答案 1 :(得分:25)
我希望代码能够产生编译时错误。
由于您的期望与规范和实施相反,您的期望将无法实现。
为什么它在编译时不会失败?
因为规范明确规定在7.6.10.2节中是合法的,为方便起见,我在此引用:
在等号后面指定集合初始值设定项的成员初始值设定项是嵌入式集合的初始化。而不是将新集合分配给字段或属性,初始化程序中给出的元素将添加到字段或属性引用的集合中。
这些代码什么时候可以正常运行?
如规范所述,初始化程序中给出的元素将添加到属性引用的集合中。酒店不引用收藏品;它是null。因此,在运行时它会提供空引用异常。有人必须初始化列表。我建议更改“Test”类,以便其构造函数初始化列表。
什么情景激发了这个功能?
LINQ查询需要表达式,而不是语句。将成员添加到新创建的列表中的新创建的集合中需要调用“添加”。由于“Add”是返回void的,因此对它的调用只能出现在表达式语句中。此功能允许您创建新集合(使用“new”)并填充它,或填充现有集合(不带“new”),其中集合是您作为LINQ结果创建的对象的成员查询。
答案 2 :(得分:18)
此代码:
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
。
这称为collection initializer,如果您执行此操作,它将在您的初始示例中有效:
List<int> test = new List<int> { 1, 2, 3 };
为了能够使用这种语法,你真的需要新的东西,即集合初始值设定项只能出现在对象创建表达式的上下文中。在C#规范的7.6.10.1节中,这是对象创建表达式的语法:
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
表达式开头。在表达式中,您可以使用不带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
现在,你真正缺少的是某种列表文字,这将非常方便。我为可枚举here提出了一个这样的文字。
答案 3 :(得分:3)
var test = (new [] { 1, 2, 3}).ToList();
答案 4 :(得分:2)
原因是第二个示例是成员列表初始化 - 而System.Linq.Expressions中的MemberListBinding表达式提供了对此的深入了解 - 请参阅我对此其他问题的答案以获取更多详细信息:{{3} }
这种类型的初始化程序要求列表已经初始化,以便可以将您提供的序列添加到其中。
结果 - 语法上代码完全没有错 - NullReferenceException
是由List实际上没有创建的运行时错误。 new
列表或代码正文中的内联new
的默认构造函数将解决运行时错误。
至于为什么它与第一行代码之间存在差异 - 在您的示例中,它是不允许的,因为这种类型的表达式不能位于赋值的右侧,因为实际上并不是创建任何东西,它只是Add
的缩写。
答案 5 :(得分:1)
将您的代码更改为:
class Test
{
public List<int> Field = new List<int>();
}
原因是您必须先显式创建一个集合对象,然后才能将项目放入其中。