2017-03-06 48 views
7

我最近正在研究一些代码,它已经从使用十进制变为使用具有十进制数和类型来表示分数的复杂类型。我不得不更新一些测试,并在输入时忘记添加新的关键字。编译的代码,但测试保持失败,抛出一个NullReferenceException。在那里我意识到失踪的新事物,并且该财产未被初始化。有没有人知道为什么会发生这种情况?我无法在C#lang规范中找到任何解释这一点的内容。C#复杂类型初始值设定项编译没有新的关键字

下面是代码示例:

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。

如果有人可以解释这一点(我看着你的方向乔恩Skeet :))我会很高兴。

+0

对象初始值设定项是您要查找的术语。这些初始化程序在相应类的构造函数被调用后调用*。然而,在你忘记'new'关键字的情况下,你并没有把这个构造函数叫做'Value'作为'NullReference'。 – HimBromBeere

+2

这个问题为什么标记重复?显然,它是在问为什么不需要new关键字,而不是关于如何使用对象初始化器的实际问题...... – nbokmans

+1

不同之处在于,如果您省略了“= new MyDecimal”部分,编译器希望'Value'已经作为对现有对象的引用,并将简单导航到它并尝试为其分配属性,如“Decimal”和“Fractional”。这就是为什么你得到一个NRE。如果你把这个添加到你的'Value' **声明**中,它会起作用,但可能不是你想要的:'{get;组; } = new MyDecimal();' –

回答

2

一个对象初始化程序并不真的实例化您的成员。

请看下面的代码:

var myInstance = new MyInstance { MyMember = new MyMember { Value = 3 }; } 

这编译为:

var myMember= new MyMember(); 
myMember.MyMember = 3; 
var myInstance = new MyInstance(); 
myInstance.MyMember = myMember; 

你的情况,你忘了实例化MyMember,因此对象的初始化程序试图访问该属性并为其分配更多的值。这是因为对象初始值设定程序总是在之后运行这个适当的构造函数,这在你的情况中并没有被调用。因此,在你的情况下,它编译成这样:

var myInstance = new MyInstance(); 
myMymber.MyMember = 3; 

造成NullReferenceExceptionmyMember从未被实例化。

为什么这甚至编译?那么,我假设编译器假定你在MyInstance的构造函数中实例化MyMember。它不知道你是否真的这样做过。

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

离开成员null当然absoluetely有效。

+0

是的,这完全有意义,但我的问题更多的是在这个意义上,为什么这个构造编译,因为它显然不应该(或至少应该有一个编译器警告)?或者另一方面,如果这种构造在语言中是可能的,那么它的用法是什么? –

+2

@SlavenVukovic - 因为对于构造非空成员并且在成员初始化器执行之前它非常有效。 –

+0

@SlavenVukovic请参阅我的编辑 – HimBromBeere

2

对象初始化程序语法允许您初始化一个对象而不首先创建它。如果你想保留对象身份,这是相当重要的。

例如,你可以让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节)到字段或属性的处理方式相同。

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

由于您的Value初始化是嵌套初始化,它可以让你刚分配的Value成员没有初始化它 - 作为Value已经被初始化,当然,只要。编译器无法验证Value是否为null,所以它不会给你一个错误。

5

的C#规范5.0定义对象初始化为(7.6.10.2对象初始):

对象初始指定零个或多个字段或对象的属性的值。

object-initializer: 
{ member-initializer-listopt } 
{ member-initializer-list , } 

和详细解释后,有给出的例子,这是非常相似的代码:

如果长方形的构造函数分配两个嵌入式Point实例

public class Rectangle 
{ 
    Point p1 = new Point(); 
    Point p2 = new Point(); 
    public Point P1 { get { return p1; } } 
    public Point P2 { get { return p2; } } 
} 

的可以使用以下构造来初始化嵌入的Point 实例,而不是分配新的实例:

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; 

同样的效果,但有一个区别,这里的Point实例发生在Rectangle构造函数的类Rectangle里面初始化。

所以语法是有效的规范,但你需要确保Value在使用对象初始化器初始化其属性以避免NRE之前被初始化。

相关问题