23
using System;
using System.Collections.Generic;

class Parent
{
   public Child Child { get; set; }
}

class Child
{
   public List<string> Strings { get; set; }
}

static class Program
{
   static void Main() {
      // bad object initialization
      var parent = new Parent() {
         Child = {
            Strings = { "hello", "world" }
         }
      };
   }
}

The above program compiles fine, but crashes at runtime with Object reference not set to an instance of the object.

If you notice in the above snippet, I have omitted new while initializing the child properties.

Obviously the correct way to initialize is:

      var parent = new Parent() {
         Child = new Child() {
            Strings = new List<string> { "hello", "world" }
         }
      };

My question is why does the C# compiler not complain when it sees the first construct?

Why is the broken initialization valid syntax?

      var parent = new Parent() {
         Child = {
            Strings = { "hello", "world" }
         }
      };
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
Ajai
  • 328
  • 3
  • 10

6 Answers6

18

It's not broken syntax, it's you who uses an object initializer on a property that's simply not instantiated. What you wrote can be expanded to

var parent = new Parent();
parent.Child.Strings = new List<string> { "hello", "world" };

Which throws the NullReferenceException: you're trying to assign the property Strings contained by the property Child while Child is still null. Using a constructor to instantiate Child first, takes care of this.

Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
12

There is nothing wrong with the initialisation, but it's trying to initialise objects that doesn't exist.

If the classes have constructors that create the objects, the initialisation works:

class Parent {
  public Child Child { get; set; }
  public Parent() {
    Child = new Child();
  }
}

class Child {
  public List<string> Strings { get; set; }
  public Child() {
    Strings = new List<string>();
  }
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
7

You seem to misunderstand what the collection initializer does.

It is a mere syntactic sugar that converts the list in the braces into a series of calls to Add() method that must be defined on the collection object being initialized.
Your = { "hello", "world" } is therefore has the same effect as

.Add("hello");
.Add("world");

Obviously this will fail with a NullReferenceException if the collection is not created.

Community
  • 1
  • 1
GSerg
  • 76,472
  • 17
  • 159
  • 346
4

The second syntax is valid for readonly properties. If you change the code to initialise the Child and Strings properties in the respective constructors, the syntax works.

class Parent
{
    public Parent()
    {
        Child = new Child();
    }

    public Child Child { get; private set; }
}

class Child
{
    public Child()
    {
        Strings = new List<string>();
    }
    public List<string> Strings { get; private set; }
}

static class Program
{
    static void Main()
    {
        // works fine now
        var parent = new Parent
        {
            Child =
            {
                Strings = { "hello", "world" }
            }
        };

    }
}
Colin Grealy
  • 615
  • 4
  • 12
  • 5
    Collection initializers have nothing to do with read-only properties. This code works because you put `Strings = new List();` in the constructor, not because the property has `private set`. – GSerg Sep 02 '15 at 09:20
  • Read the code again. The Child property is not a collection, it's a read only property. – Colin Grealy Sep 02 '15 at 12:39
  • As Colin answered looks like it's all covered in the spec - the example on embedded Rectangle initialization : https://msdn.microsoft.com/en-us/library/Bb308966.aspx#csharp3.0overview_topic12 – Ajai Sep 02 '15 at 15:13
  • 1
    The Colin's answer is deceptive and therefore wrong, @Ajai. It states there is a connection between read-only properties and behaviour of collection initializers. This is not true. Collection initializers and read-only properties do not have to do anything with each other, they are completely unrelated concepts. Please see [this answer](http://stackoverflow.com/a/2495801/11683) I have linked to. – GSerg Sep 02 '15 at 16:35
  • @GSerg Once again, my answer is not deceptive or wrong. Please read the spec before answering: If the Rectangle constructor allocates the embedded Point instance `public class Rectangle { Point p1 = new Point(); public Point P1 { get { return p1; } } }` the following construct can be used to initialize the embedded Point instance instead of assigning new instances: `var r = new Rectangle { P1 = { X = 0, Y = 1 }, };` – Colin Grealy Sep 02 '15 at 21:26
  • 1
    The rectangle initializer is an `object-initializer`. The initializer the OP is talking about is a `collection-initializer`. [They are different](http://stackoverflow.com/a/6254225/11683). The collection initializer is translated by the compiler into a series or calls to the `Add()` method, not into anything else. This behaviour has no relation to either read-only properties or `object-initializer`s. – GSerg Sep 03 '15 at 11:05
  • @GSerg and the bit of code that says `Child = {` is an OBJECT-INITIALIZER. – Colin Grealy Sep 04 '15 at 12:08
  • Yes, I overlooked that both `Child` and `Strings` give the exception, not just `Strings`. This does not change my [main point](http://stackoverflow.com/questions/32342188/c-sharp-object-initialization-bug/32342273?noredirect=1#comment52571824_32342273): this answer is wrong and misleading because it claims there is a connection between object initializers and read-only properties. There isn't. Try removing `private` from your code, it won't change anything. – GSerg Sep 04 '15 at 16:15
  • 2
    Your next sentence, *If you change the code to initialise the Child and Strings properties in the respective constructors, the syntax works* is correct, and I would upvote your answer if that statement was followed by "because object-initializers do not create objects, they only set properties or add items to collections" instead of "it's for read-only properties." – GSerg Sep 04 '15 at 16:17
0

Referencing a null can`t always be checked for at compile time. Although the compiler sometimes warns of using a variable before it has been assigned. The compiler works correctly. This is a run-time error.

Russell Harkins
  • 404
  • 3
  • 4
0

Note that this syntax can cause some unexpected results and hard-to-spot errors:

class Test
{
    public List<int> Ids { get; set; } = new List<int> { 1, 2 };
}

var test = new Test { Ids = { 1, 3 } };

foreach (var n in test)
{
    Console.WriteLine(n);
}

You might expect the output to be 1,3, but instead it is:

1
2
1
3
EventHorizon
  • 2,916
  • 22
  • 30
  • So does that mean that Ids = { 1, 3 } calls AddRange and not new List { 1, 3} ? Is that mentioned in docs somewhere? – Petru Lutenco Aug 04 '23 at 13:02
  • 1
    The compiler emits a separate Add statement for each value. The constructor for Test contains two Add statements for values 1 and 2, and the main body contains two additional Add statements for 1 and 3. If you remove the initializer in Test you'll get a null reference exception on the "var test =" code line. I believe there was a better explanation in an article somewhere, but I've forgotten where. It's mentioned here in the official documentation: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers – EventHorizon Aug 08 '23 at 09:48
  • Great explanation, thanks! – Petru Lutenco Aug 08 '23 at 16:14