4

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.

2
  • The stack overflow is simply due to the fact that creating a Foo will create a Dictionary and creates a Foo to add to it, that in turn creates a Dictionary with a Foo and so on. Commented Jan 4, 2017 at 14:11
  • Note you can change it to Baz = { {1, new Foo { Bar = {4,5,6}}}}; to get it to work because that notation uses the Add(Tkey, TValue) of Dictionary and in that case it will not compile without the new Foo. Commented Jan 4, 2017 at 14:30

1 Answer 1

7

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.

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

9 Comments

The StackOverflowException was a stupid oversight. I wasn't aware of this part of the specification though. Thanks
I still don't get this. In which line of code is an object instance of "Foo" gets created recursively?
Shouldn't that be temp.Baz[1].Bar.Add(4)?
@Sung If you mean the Foo in the dictionary, then there isn't a Foo being created, it's assuming there is a Foo already at index 1 and it's just adding values to it's Bar.
I wonder what use case they had in mind with the "typeless" Dictionary initialization. This notation would be really nice and concise if it did (tmp.Baz.ContainsKey(1) ? tmp.Baz[1] : tmp.Baz[1] = new ...).Bar.Add(4) instead
|

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.