24
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" }
         }
      };
0

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

Comments

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

Comments

8

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.

Comments

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

    }
}

9 Comments

Collection initializers have nothing to do with read-only properties. This code works because you put Strings = new List<string>(); in the constructor, not because the property has private set.
Read the code again. The Child property is not a collection, it's a read only property.
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 I have linked to.
The rectangle initializer is an object-initializer. The initializer the OP is talking about is a collection-initializer. They are different. 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-initializers.
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."
|
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

2 Comments

So does that mean that Ids = { 1, 3 } calls AddRange and not new List<int> { 1, 3} ? Is that mentioned in docs somewhere?
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: learn.microsoft.com/en-us/dotnet/csharp/programming-guide/…

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.