No warning when using a Collection Initializer on an embedded collection in an Object Initializer

Viewed 63

Given

public class SomeClassWithAList
{
    public List<int> list { get; set; }
}

[Fact]
public void InitListInsideModel()
{
    var cashthing = new SomeClassWithAList { list = { 4 } };
}

My issue is that it appears InitListInsideModel is a valid code, but this code actually won't execute.

System.NullReferenceException : Object reference not set to an instance of an object.

What's going on here? What basic concept am I not understanding? Is this a compiler bug? Why does IntelliSense not warn me I cannot instantiate a list this way?

2 Answers

TLDR

The fix, as you know would be to initialize the collection one way or another

public List<int> list { get; set; } = new List<T>();

// or

var cashthing = new SomeClassWithAList
{
    list = new List<int>(){ 4 }
};

As to why you can do this and not get a warning? Well... this is an interesting issue.

At first glance, it looks like a roslyn bug which is allowing invalid array initializer syntax on a collection within an object Initializer.

However, it is actually by design. What the syntax is doing, is adding a sequence using the resolution of the collections Add method. For this to work, the collection needs to be initialized!

You learn something new every day

The following

var cashthing = new SomeClassWithAList
{
    list = { 4 }
};

Compiles to

new SomeClassWithAList().list.Add(4);

IMO, static analysis should be able to warn for this (and especially with nullable types), it should also be well documented in Object and Collection Initializers (C# Programming Guide).

The only place I could find suitable documentation was in the C# Language Specifications

Emphasis mine

7.6.10.2 Object initializers

...

A member initializer that specifies a collection initializer after the equals sign is an initialization of an embedded collection. Instead of assigning a new collection to the field or property, the elements given in the initializer are added to the collection referenced by the field or property. The field or property must be of a collection type that satisfies the requirements specified in §7.6.10.3.

So the correct usage of this feature, is to make sure your collection is auto initialized

public List<int> list { get; set; } = new List<T>();

Additional Resources

After digging around some more, I found the following

I think you need to instantiate list somewhere ...

Either in the assignment you currently have:

var cashthing = new SomeClassWithAList { new List<int> list = { 4 } };

Or in the list declaration:

public List<int> list { get; set; } = new List<int>();

Or in the SomeClassWithAList class constructor:

public SomeClassWithAList()
{
    list = new List<int>();
}

All of those options compile and run.

Related