Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Nested initialization strangeness

Under the premise that these initialization statements compile

List<int> l = new List<int> { 1, 2, 3 };
Dictionary<int, int> d = new Dictionary<int, int> { [1] = 11, [2] = 22 };
Foo f = new Foo { Bar = new List<int>() };

and this will not

List<int> l = { 1, 2, 3 };
Dictionary<int, int> d = { [1] = 11, [2] = 22 };
Foo f = { Bar = new List<int>() };

I have a question about nested initializations. Given the following class

public class Foo {
    public List<int> Bar { get; set; } = new List<int>();
    public Dictionary<int, Foo> Baz { get; set; } = new Dictionary<int, Foo>();
}

I discovered by accident that you could actually do this:

Foo f = new Foo {
    Bar = { 1, 2, 3 },
    Baz = {
        [1] = {
            Bar = { 4, 5, 6 }
        }
    }
};

While it does compile it throws a KeyNotFoundException. So I changed the properties to

public List<int> Bar { get; set; } = new List<int> { 4, 5, 6 };
public Dictionary<int, Foo> Baz { get; set; }
    = new Dictionary<int, Foo> { [1] = new Foo { Bar = new List<int>() { 1, 2, 3 } } };

assuming that this is some unusual notation for replacing existing members. Now the initialization throws a StackOverflowException.

So my question is, why does the expression even compile? What is it supposed to do? I feel like I must be missing something really obvious.

like image 450
Marcus S. Avatar asked Mar 11 '23 06:03

Marcus S.


1 Answers

So my question is, why does the expression even compile?

It's an object initializer with a collection initializer value. From the C# specification section 7.6.10.2:

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.

So your code code is roughly equivalent to:

Foo tmp = new Foo();
tmp.Bar.Add(1);
tmp.Bar.Add(2);
tmp.Bar.Add(3);
tmp.Baz[1].Bar.Add(4); // This will throw KeyNotFoundException if Baz is empty
tmp.Baz[1].Bar.Add(5);
tmp.Baz[1].Bar.Add(6);
Foo f = tmp;

Your initialization version will throw a StackOverflowException because the initializer for Foo needs to create a new instance of Foo, which needs to create a new instance of Foo etc.

like image 180
Jon Skeet Avatar answered Mar 14 '23 16:03

Jon Skeet