C # type type initializer without a new keyword

I recently worked on some code that changed from using a decimal number to use a complex type that has a decimal number and a type to represent a fraction. I had to update some tests, and when typing, I forgot to add a new keyword. The code compiled, but the test continued to fail, throwing a NullReferenceException. There I realized the missing new one and that the property was not initialized. Does anyone have an idea why this is happening? I could not find anything in the C # lang spec that would explain this.

Here is a sample code:

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

Note that I use C # 6 and VS 2015, but I get the same result in LINQPad.

If anyone can explain this (I'm looking in your direction John Skeet :)) I would be glad.

+9
c #
source share
3 answers

The object initializer does not actually 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 create an instance of MyMember , so the initializer is trying to access this property and assign additional values ​​to it. This is due to the fact that object initializers always start after the corresponding constructor, which was not called in your case. So in your case, this compiles as follows:

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

Causing a NullReferenceException as myMember has never been myMember .

Why is this even compiling? Well, I assume that the compiler assumes that you create an instance of MyMember in the constructor of MyInstance . It may not know when you actually did it.

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

Leaving null members is, of course, absolutely fair.

+4
source share

The C # 5.0 specification defines an object initializer as (7.6.10.2 Object initializers):

The object initializer sets values ​​for zero or more fields or properties of the object.

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

And after a detailed explanation, an example is given that is very similar to your code:

If the Rectangles Designer Selects Two Embedded Point Instances

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

to initialize the built-in point, you can use the following instance design instead of assigning new instances:

 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: here Point instances are initialized inside the Rectangle class, which is found in the Rectangle constructor.

Thus, the syntax is valid by specification, but you need to make sure that Value initialized before using the object initializer to initialize its properties to avoid NRE.

+6
source share

The object initializer syntax allows you to initialize an object without first creating it. This is very important if you want to keep the object identifier.

For example, you can make the ClassA.Value property read-only and initialize it in the constructor of the object:

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

This, of course, is explicitly stated in the C # specification (excerpt from version 5):

7.6.10.2 Object initializers

The initializer of the element that defines the expression after the equal sign is treated like an assignment (Β§7.17.1) in a field or property.

The initializer of the element that indicates the initializer of the object after the equal sign is the initializer of the nested object, that is, the initialization of the embedded object. Instead of assigning a new value to a field or property, assignments in the initializer of nested objects are considered as assignments to members of a field or property. Initializers of nested objects cannot be applied to properties with a value type or to read-only fields with a type value.

Since your Value initializer is a nested initializer, it allows you to simply assign Value members without initializing it, provided, of course, the Value initialization has already been initialized. The compiler has no way to check if Value null , so it cannot give you an error.

+3
source share

All Articles