为什么这个带有花括号的错误对象初始化甚至可以编译?
Why does this wrong object initialisation with curly braces even compile?
在为 WPF/MVVM 项目的集合创建一些虚拟数据时,我生成了以下错误代码,编译正常,但在运行时抛出异常。
有一个数据对象的嵌套结构,我错误地只用花括号实例化了它(看起来写 JavaScript 确实会对大脑造成永久性伤害)。
using System.Collections.ObjectModel;
namespace testapp
{
class Program
{
static void Main(string[] args)
{
var collection = new ObservableCollection<TopLevelDataObject>();
collection.Add(new TopLevelDataObject{Nested = {Bar = 5}}); // NullReferenceException
}
class TopLevelDataObject
{
public NestedDataObject Nested { get; set; }
public string Foo { get; set; }
}
class NestedDataObject
{
public double Bar { get; set; }
}
}
}
为什么编译成功?
如果我创建一个匿名类型,如 Nested = new {Bar = 5}
,我在编译期间收到错误消息(因此失败):
Cannot implicitly convert type '<anonymous type: int Bar>' to 'testapp.Program.NestedDataObject'
为什么省略 new
运算符时没有出现这样的错误?
它甚至为我提供了 属性 的代码提示:
我的猜测是 {Bar = 5}
只是一个代码块,它本身就是一个有效的东西。
但为什么将代码块分配给任何东西(在本例中为 Nested
属性)是有效的?
Why does that compile?
因为当代码被编译时,它被编译为一组赋值操作。不一定都是您创建的新实例。
如果你从构造函数构造一个新的Nested
实例,你可以给Nested.Bar
赋值。
将 public NestedDataObject Nested { get; set; }
更改为此以查看其工作原理:
public NestedDataObject Nested { get; } = new NestedDataObject();
(请注意,您永远不能在上述代码的构造函数之外为 Nested
赋值!)
Why does that compile?
因为 var x = new SomeType{Property = value};
等同于:
var x = new SomeType();
x.Property = value;
事实上,我们甚至可以在 ()
中留下 var x = new SomeType(){Property = value};
甚至 var x = new SomeType(argument){Property = value};
将参数传递给构造函数并设置一个值。
因此你可以看到总是有一个构造函数被调用,如果你省略了使它显式的括号,它总是 nullary (no-argument) 构造函数。
同时,没有显式构造函数的类型总是有一个 public 空构造函数("default constructor")。
因此 new TopLevelDataObject{Nested = {Bar = 5}}
等同于:
var temp = new TopLevelDataObject();
temp.Nested.Bar = 5; // NRE on temp.Nested
因为 TopLevelDataObject
可能有一个设置 `Nestedt 的构造函数,所以您的代码可以工作,所以它应该编译。当然,因为它没有这样的构造函数所以它不起作用。
(请注意,初始化器与匿名类型的操作并不完全相同,在这种情况下,它会被重写为调用隐藏的构造函数,因此允许属性为 read-only,即使初始化器不能与 read-only non-anonymous 类型的属性。语法允许它们看起来相同,因此很容易理解为相似但结果不一样)。
在为 WPF/MVVM 项目的集合创建一些虚拟数据时,我生成了以下错误代码,编译正常,但在运行时抛出异常。
有一个数据对象的嵌套结构,我错误地只用花括号实例化了它(看起来写 JavaScript 确实会对大脑造成永久性伤害)。
using System.Collections.ObjectModel;
namespace testapp
{
class Program
{
static void Main(string[] args)
{
var collection = new ObservableCollection<TopLevelDataObject>();
collection.Add(new TopLevelDataObject{Nested = {Bar = 5}}); // NullReferenceException
}
class TopLevelDataObject
{
public NestedDataObject Nested { get; set; }
public string Foo { get; set; }
}
class NestedDataObject
{
public double Bar { get; set; }
}
}
}
为什么编译成功?
如果我创建一个匿名类型,如 Nested = new {Bar = 5}
,我在编译期间收到错误消息(因此失败):
Cannot implicitly convert type '<anonymous type: int Bar>' to 'testapp.Program.NestedDataObject'
为什么省略 new
运算符时没有出现这样的错误?
它甚至为我提供了 属性 的代码提示:
我的猜测是 {Bar = 5}
只是一个代码块,它本身就是一个有效的东西。
但为什么将代码块分配给任何东西(在本例中为 Nested
属性)是有效的?
Why does that compile?
因为当代码被编译时,它被编译为一组赋值操作。不一定都是您创建的新实例。
如果你从构造函数构造一个新的Nested
实例,你可以给Nested.Bar
赋值。
将 public NestedDataObject Nested { get; set; }
更改为此以查看其工作原理:
public NestedDataObject Nested { get; } = new NestedDataObject();
(请注意,您永远不能在上述代码的构造函数之外为 Nested
赋值!)
Why does that compile?
因为 var x = new SomeType{Property = value};
等同于:
var x = new SomeType();
x.Property = value;
事实上,我们甚至可以在 ()
中留下 var x = new SomeType(){Property = value};
甚至 var x = new SomeType(argument){Property = value};
将参数传递给构造函数并设置一个值。
因此你可以看到总是有一个构造函数被调用,如果你省略了使它显式的括号,它总是 nullary (no-argument) 构造函数。
同时,没有显式构造函数的类型总是有一个 public 空构造函数("default constructor")。
因此 new TopLevelDataObject{Nested = {Bar = 5}}
等同于:
var temp = new TopLevelDataObject();
temp.Nested.Bar = 5; // NRE on temp.Nested
因为 TopLevelDataObject
可能有一个设置 `Nestedt 的构造函数,所以您的代码可以工作,所以它应该编译。当然,因为它没有这样的构造函数所以它不起作用。
(请注意,初始化器与匿名类型的操作并不完全相同,在这种情况下,它会被重写为调用隐藏的构造函数,因此允许属性为 read-only,即使初始化器不能与 read-only non-anonymous 类型的属性。语法允许它们看起来相同,因此很容易理解为相似但结果不一样)。