3

Whille creating some dummy data for a collection for a WPF/MVVM project, I produced the following wrong code, which compiles fine, but throws an exception at runtime.

There's a nested structure of data objects, which I wrongly instantiate with only the curly braces (looks like writing JavaScript does cause permanent damage to the brain).

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

Why does that compile?

If I create an annonymous type, like Nested = new {Bar = 5}, I get the error message during compilation (which thus fails):

Cannot implicitly convert type '<anonymous type: int Bar>' to 'testapp.Program.NestedDataObject'

Why do I not get such an error when ommitting the new operator?

It even gives me a code hint for the property: click to see the image, for I do not have enough rep to embed it yet

My guess would be that {Bar = 5} is simply a code block, which on its own is a valid thing to have. But why is it valid to assign a code block to anything (in this case, the Nested property)?

Fildor
  • 14,510
  • 4
  • 35
  • 67
surface
  • 93
  • 6

2 Answers2

5

Why does that compile?

Because when that code is compiled, it is compiled as just a set of assignment operations. It doesn't all have to be new instances you create.

If you construct a new instance of Nested from the constructor, you can assign a value to Nested.Bar.

Change public NestedDataObject Nested { get; set; } to this to see how it works:

public NestedDataObject Nested { get; } = new NestedDataObject();

(Note you can never assign a value to Nested outside the constructor in the above code!)

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
2

Why does that compile?

Because var x = new SomeType{Property = value}; is the same as:

var x = new SomeType();
x.Property = value;

Indeed we can even leave in the () to have var x = new SomeType(){Property = value}; or even var x = new SomeType(argument){Property = value}; combining passing an argument to the constructor and setting a value.

As such you can see that there is always a constructor called, and if you leave out the parentheses that make that explicit its always the nullary (no-argument) constructor.

Meanwhile a type with no explicit constructor always has a public nullary constructor (the "default constructor").

Hence new TopLevelDataObject{Nested = {Bar = 5}} is the same as:

var temp = new TopLevelDataObject();
temp.Nested.Bar = 5; // NRE on temp.Nested

Because TopLevelDataObject could have a constructor that sets `Nestedt then the code you have could work, so it should compile. Of course, because it doesn't have such a constructor it doesn't work.

(Note that initialisers don't operate quite the same with anonymous types, in that case it gets rewritten to call a hidden constructor hence allowing the properties to be read-only even though initialisers cannot be used with read-only properties of non-anonymous types. The syntax allows them to look the same and hence be easily understood as similar but the result is not the same).

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251