提问人:Ajai 提问时间:9/2/2015 最后编辑:Alexei LevenkovAjai 更新时间:2/17/2020 访问量:5104
初始化不带“new List”的列表属性会导致 NullReferenceException
Initializing list property without "new List" causes NullReferenceException
问:
using System;
using System.Collections.Generic;
class Parent
{
public Child Child { get; set; }
}
class Child
{
public List<string> Strings { get; set; }
}
static class Program
{
static void Main() {
// bad object initialization
var parent = new Parent() {
Child = {
Strings = { "hello", "world" }
}
};
}
}
上面的程序编译良好,但在运行时崩溃,对象引用未设置为对象的实例。
如果您在上面的代码片段中注意到,我在初始化子属性时省略了 new。
显然,正确的初始化方式是:
var parent = new Parent() {
Child = new Child() {
Strings = new List<string> { "hello", "world" }
}
};
我的问题是,为什么 C# 编译器在看到第一个构造时不抱怨?
为什么中断的初始化是有效的语法?
var parent = new Parent() {
Child = {
Strings = { "hello", "world" }
}
};
答:
18赞
Jeroen Vannevel
9/2/2015
#1
这不是语法中断,而是您在根本没有实例化的属性上使用对象初始值设定项。你写的内容可以扩展到
var parent = new Parent();
parent.Child.Strings = new List<string> { "hello", "world" };
这抛出 : 您正在尝试分配该属性所包含的属性,而 仍然是 。
首先使用构造函数进行实例化,可以解决这个问题。NullReferenceException
Strings
Child
Child
null
Child
4赞
Colin Grealy
9/2/2015
#2
第二种语法对只读属性有效。如果更改代码以在各自的构造函数中初始化 Child 和 Strings 属性,则语法有效。
class Parent
{
public Parent()
{
Child = new Child();
}
public Child Child { get; private set; }
}
class Child
{
public Child()
{
Strings = new List<string>();
}
public List<string> Strings { get; private set; }
}
static class Program
{
static void Main()
{
// works fine now
var parent = new Parent
{
Child =
{
Strings = { "hello", "world" }
}
};
}
}
评论
5赞
GSerg
9/2/2015
集合初始值设定项与只读属性无关。此代码之所以有效,是因为您输入了构造函数,而不是因为该属性具有 .Strings = new List<string>();
private set
0赞
Colin Grealy
9/2/2015
再次读取代码。Child 属性不是集合,而是只读属性。
1赞
GSerg
9/3/2015
@Ajai,科林的回答具有欺骗性,因此是错误的。它指出只读属性与集合初始值设定项的行为之间存在联系。事实并非如此。集合初始值设定项和只读属性不必相互执行任何操作,它们是完全不相关的概念。请看我链接到的这个答案。
1赞
GSerg
9/3/2015
矩形初始值设定项是一个 .OP 所谈论的初始值设定项是一个 .它们是不同的。集合初始值设定项由编译器转换为一系列或对方法的调用,而不是转换为任何其他内容。此行为与只读属性或 s 无关。object-initializer
collection-initializer
Add()
object-initializer
2赞
GSerg
9/5/2015
您的下一句话,如果您更改代码以在各自的构造函数中初始化 Child 和 Strings 属性,则语法是正确的,如果该语句后面跟着“因为对象初始值设定项不创建对象,所以它们只设置属性或将项添加到集合”而不是“它用于只读属性”,我会支持您的答案。
12赞
Guffa
9/2/2015
#3
初始化没有错,但它试图初始化不存在的对象。
如果类具有创建对象的构造函数,则初始化有效:
class Parent {
public Child Child { get; set; }
public Parent() {
Child = new Child();
}
}
class Child {
public List<string> Strings { get; set; }
public Child() {
Strings = new List<string>();
}
}
0赞
Joe Smith
9/2/2015
#4
在编译时不能始终检查引用 null。尽管编译器有时会警告在赋值之前使用变量。编译器工作正常。这是一个运行时错误。
7赞
GSerg
9/2/2015
#5
您似乎误解了集合初始值设定项的作用。
它只是一个语法糖,它将大括号中的列表转换为对 Add()
方法的一系列调用,这些调用必须在正在初始化的集合对象上定义。
因此,你的效果与= { "hello", "world" }
.Add("hello");
.Add("world");
显然,如果未创建集合,这将失败并出现 NullReferenceException。
0赞
EventHorizon
2/17/2020
#6
请注意,此语法可能会导致一些意外结果和难以发现的错误:
class Test
{
public List<int> Ids { get; set; } = new List<int> { 1, 2 };
}
var test = new Test { Ids = { 1, 3 } };
foreach (var n in test)
{
Console.WriteLine(n);
}
您可能希望输出为 1,3,但实际情况是:
1
2
1
3
评论
0赞
Petru Lutenco
8/4/2023
那么这是否意味着 Ids = { 1, 3 } 调用 AddRange 而不是 new List<int> { 1, 3} ?文档中某处提到过吗?
1赞
EventHorizon
8/8/2023
编译器为每个值发出单独的 Add 语句。Test 的构造函数包含两个值为 1 和 2 的 Add 语句,主体包含两个用于 1 和 3 的附加 Add 语句。如果在 Test 中删除初始值设定项,则会在“var test =”代码行上收到 null 引用异常。我相信某处的一篇文章中有更好的解释,但我忘记了在哪里。官方文档中提到了它:learn.microsoft.com/en-us/dotnet/csharp/programming-guide/......
0赞
Petru Lutenco
8/9/2023
很好的解释,谢谢!
评论