简体   繁体   English

C#复杂类型初始化程序编译时没有new关键字

[英]C# complex type initializer compiles without new keyword

I was recently working on some code, that has changed from using decimal to use a complex type that has the decimal number and a type to represent a fraction. 我最近正在研究一些代码,这些代码已经从使用十进制变为使用具有十进制数的复杂类型和表示分数的类型。 I had to update some tests, and while typing I forgot to add the new keyword. 我不得不更新一些测试,在键入时我忘了添加关键字。 The code compiled but the test kept failing, throwing a NullReferenceException. 代码已编译但测试仍然失败,抛出NullReferenceException。 There I realized the missing new and that the property was not initialized. 在那里,我意识到缺少新的,并且该属性未初始化。 Has anybody an idea why this happening? 有谁知道为什么会这样? I could not find anything in the C# lang specification that would explain this. 我在C#lang规范中找不到可以解释这一点的任何内容。

Here is the code sample: 这是代码示例:

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 
         }
     }
}

Please note that I'm using C# 6 and VS 2015, but I get the same result also in LINQPad. 请注意我使用的是C#6和VS 2015,但我在LINQPad中也得到了相同的结果。

If somebody could explain this (I'm looking in your direction Jon Skeet :) ) I would be glad. 如果有人可以解释一下(我正朝着你的方向寻找Jon Skeet :))我会很高兴的。

The C# Specification 5.0 defines object initializer as ( 7.6.10.2 Object initializers ): C#Specification 5.0将对象初始化器定义为( 7.6.10.2对象初始化器 ):

An object initializer specifies values for zero or more fields or properties of an object. 对象初始值设定项指定零个或多个字段或对象属性的值。

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

And after the detailed explanation there is an example given which is very similar to your code: 在详细解释之后,给出了一个与您的代码非常相似的示例:

If Rectangle's constructor allocates the two embedded Point instances 如果Rectangle的构造函数分配两个嵌入的Point实例

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

the following construct can be used to initialize the embedded Point instances instead of assigning new instances: 以下构造可用于初始化嵌入的Point实例,而不是分配新实例:

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

which has the same effect as 效果与...相同

 Rectangle __r = new Rectangle(); __r.P1.X = 0; __r.P1.Y = 1; __r.P2.X = 2; __r.P2.Y = 3; Rectangle r = __r; 

But there is only one difference, the Point instances here are initialized inside of the Rectangle class which occurs in the constructor of Rectangle . 但是只有一个区别,这里的Point实例是在Rectangle类中初始化的,它发生在Rectangle的构造函数中。

So the syntax is valid by the specification, but you need to make sure Value is initialized before using the object initializer to initialize its properties in order to avoid NRE. 因此规范的语法是有效的,但是在使用对象初始化程序初始化其属性以避免NRE之前,需要确保初始化Value

An object-initializer doesn not really instantiate your members. 对象初始化器并不真正实例化您的成员。

See the following code: 请参阅以下代码:

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

This compiles to: 这编译为:

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

In your case you forgot to instantiate MyMember , thus the object-intializer tries to access that property and assign further values to it. 在您的情况下,您忘记实例化 MyMember ,因此对象初始化程序尝试访问该属性并为其分配更多值。 This is due to the fact that object-initializers allways run after the appropriate constructor, which wasn´t called in your case. 这是因为对象初始化器总是适当的构造函数之后运行,在您的情况下没有调用。 So in your case it compiles to this: 所以在你的情况下,它编译为:

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

Causing a NullReferenceException as myMember was never instantiated. 导致NullReferenceException作为myMember从未实例化。

Why does this even compile? 为什么这甚至编译? Well, I assume the compiler assumes that you instantiate MyMember within the constructor of MyInstance . 好吧,我假设编译器假定您在MyInstance的构造函数中实例化MyMember It can´t know wheather you actually did this. 它不知道你实际上是这么做的。

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

Leaving members null is of course absoluetely valid. 让成员null当然是绝对有效的。

The object initializer syntax allows you to initialize an object without creating it first. 对象初始值设定程序语法允许您初始化对象而不首先创建它。 This is rather important if you want to preserve object identity. 如果要保留对象标识,这一点非常重要。

For example, you could make ClassA.Value a read-only property, and initialize it in the object constructor: 例如,您可以使ClassA.Value成为只读属性,并在对象构造函数中初始化它:

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

  public MyDecimal Value { get; private set; }
}

This behaviour is of course explicitly outlined in the C# specification (excerpt from version 5): 当然,这种行为在C#规范中有明确概述(摘自版本5):

7.6.10.2 Object initializers 7.6.10.2对象初始值设定项

A member initializer that specifies an expression after the equals sign is processed in the same way as an assignment (§7.17.1) to the field or property. 在等号后面指定表达式的成员初始值设定项的处理方式与对字段或属性的赋值(第7.17.1节)相同。

A member initializer that specifies an object initializer after the equals sign is a nested object initializer, ie an initialization of an embedded object. 在等号后面指定对象初始值设定项的成员初始值设定项是嵌套对象初始值设定项,即嵌入对象的初始化。 Instead of assigning a new value to the field or property, the assignments in the nested object initializer are treated as assignments to members of the field or property. 而不是为字段或属性分配新值,嵌套对象初始值设定项中的赋值被视为对字段或属性成员的赋值。 Nested object initializers cannot be applied to properties with a value type, or to read-only fields with a value type. 嵌套对象初始值设定项不能应用于具有值类型的属性,也不能应用于具有值类型的只读字段。

Since your Value initializer is a nested initializer, it allows you to just assign members of Value without initializing it - as long as Value has been initialized already, of course. 由于您的Value初始Value是嵌套的初始值设定项,因此它允许您只是在不初始化的情况下分配Value成员 - 当然,只要Value已经初始化了。 The compiler has no way of verifying whether Value is null , so it cannot give you an error. 编译器无法验证Value是否为null ,因此无法给出错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM