C#复杂类型初始值设定项编译时不使用新关键字

C#复杂类型初始值设定项编译时不使用新关键字,c#,C#,我最近正在编写一些代码,这些代码已经从使用decimal改为使用一个复杂类型,该类型具有十进制数和一个表示分数的类型。我不得不更新一些测试,在键入时我忘了添加new关键字。代码已编译,但测试一直失败,引发NullReferenceException。在那里,我意识到缺少新属性,并且该属性没有初始化。有人知道为什么会这样吗?我在C#lang规范中找不到任何可以解释这一点的东西 以下是代码示例: public class Fraction { public int Numerator {

我最近正在编写一些代码,这些代码已经从使用decimal改为使用一个复杂类型,该类型具有十进制数和一个表示分数的类型。我不得不更新一些测试,在键入时我忘了添加new关键字。代码已编译,但测试一直失败,引发NullReferenceException。在那里,我意识到缺少新属性,并且该属性没有初始化。有人知道为什么会这样吗?我在C#lang规范中找不到任何可以解释这一点的东西

以下是代码示例:

public class Fraction 
{
    public int Numerator { get; set; }
    public int Denominator { get; set; }
}

public class MyDecimal
{
    public decimal? Decimal { get; set; }     
    public Fraction Fractional { get; set; }
}

public class ClassA 
{
    public MyDecimal Value { get; set; }
}

//...

var instance = new ClassA
{
     Value = // new MyDecimal is missing here
     {
         Decimal = 2.0m,
         Fractional = new Fraction 
         { 
               Numerator = 3,   
               Denominator = 4 
         }
     }
}
请注意,我使用的是C#6和VS 2015,但我在LINQPad中也得到了相同的结果


如果有人能解释这一点(我正朝你的方向看Jon Skeet:),我会很高兴。

对象初始值设定项并不会真正实例化你的成员

请参阅以下代码:

var myInstance = new MyInstance { MyMember = new MyMember { Value = 3 }; }
这将编译为:

var myMember= new MyMember();
myMember.Value = 3;
var myInstance = new MyInstance();
myInstance.MyMember = myMember;
在您的例子中,您忘记实例化
MyMember
,因此对象初始化器尝试访问该属性并为其分配更多值。这是因为对象初始值设定项总是在适当的构造函数之后运行,在您的案例中没有调用该构造函数。因此,在您的情况下,它编译为:

var myInstance = new MyInstance();
myMymber.Value = 3;
由于从未实例化
myMember
而导致
NullReferenceException
异常

为什么还要编译?我假设编译器假设您在
MyInstance
的构造函数中实例化
MyMember
。它不知道你到底是谁干的

class Instance
{
    MyMember MyMember = new MyMember();
}

将成员保留为null当然是绝对有效的。

对象初始值设定项语法允许您在不首先创建对象的情况下初始化对象。如果要保留对象标识,这一点非常重要

例如,您可以将
ClassA.Value
设置为只读属性,并在对象构造函数中对其进行初始化:

public class ClassA 
{
  public ClassA() 
  {
    Value = new MyDecimal();
  }

  public MyDecimal Value { get; private set; }
}
当然,这种行为在C#规范中有明确的概述(摘自第5版):

7.6.10.2对象初始值设定项

一种成员初始值设定项,指定等号后的表达式,其处理方式与字段或属性赋值(§7.17.1)相同

在等号后指定对象初始值设定项的成员初始值设定项是嵌套对象初始值设定项,即嵌入式对象的初始化。嵌套对象初始值设定项中的指定将被视为对字段或属性成员的指定,而不是为字段或属性指定新值。嵌套对象初始值设定项不能应用于值类型的属性或值类型的只读字段

由于
初始值设定项是一个嵌套的初始值设定项,因此它允许您只分配
的成员,而不进行初始化-当然,只要
已经初始化。编译器无法验证
是否为
null
,因此它无法向您提供错误。

C#规范5.0将对象初始值设定项定义为(7.6.10.2对象初始值设定项):

对象初始值设定项为零个或多个字段或对象属性指定值

object-initializer:
{   member-initializer-listopt   }
{   member-initializer-list   ,   }
在详细解释之后,给出了一个与您的代码非常相似的示例:

如果矩形的构造函数分配两个嵌入点实例

public class Rectangle
{
  Point p1 = new Point();
  Point p2 = new Point();
  public Point P1 { get { return p1; } }
  public Point P2 { get { return p2; } }
}
以下构造可用于初始化嵌入点 实例,而不是分配新实例:

Rectangle r = new Rectangle {
  P1 = { X = 0, Y = 1 },
  P2 = { X = 2, Y = 3 }
};
这和

Rectangle __r = new Rectangle();
__r.P1.X = 0;
__r.P1.Y = 1;
__r.P2.X = 2;
__r.P2.Y = 3;
Rectangle r = __r;
但是只有一个区别,这里的
实例是在
矩形
类中初始化的,该类出现在
矩形
的构造函数中


因此,根据规范,语法是有效的,但是您需要确保在使用对象初始值设定项初始化其属性之前初始化了
Value
,以避免NRE。

对象初始值设定项是您要查找的术语。在调用相应类的构造函数之后,将调用这些初始值设定项。但是在您的情况下,由于忘记了
new
关键字,您没有调用此构造函数making
Value
a
NullReference
。为什么此问题标记为重复?显然,它是在问为什么不需要new关键字,而不是关于如何使用对象初始值设定项的实际问题……区别在于,如果您使用
=new MyDecimal
部分,编译器希望
Value
已经是对现有对象的引用,并将简单地导航到它并尝试为其分配属性,例如
Decimal
fractal
。这就是为什么你会得到NRE。如果您将此添加到
声明中,它将起作用,但可能不是您想要的:
{get;set;}=new MyDecimal()
@LasseV.Karlsen但是为什么代码编译成这样是另一回事question@SelmanGençUm,不,Lasse也解释了这一点。如果
Value
已经初始化(例如,在
ClassA
的类构造函数中),您不想用其他实例替换它,只需分配属性即可。这在C#规范中明确列出。是的,这完全有道理,但我的问题更多的是,为什么这个构造会编译,因为它显然不应该编译(或者至少应该有一个编译器警告)?或者另一方面,如果这种构造在语言中是可能的,它会有什么用途?@SlavenVukovic-因为在构造对象之后,在成员初始值设定项执行之前,对象具有非空成员是完全有效的。@HimBromBeere这完全有道理,编译器无法真正验证您是否已初始化构造函数中的属性。我仍然必须承认它有点难看,你应该被迫在这里使用新的关键字,但这是真的