3

Why is it possible to do the following without compiler-errors:

private class Foo
{
     public List<string> Bar { get; set; }
}

private void SomeRandomMethod(){
    var test2 = new Foo
    {
         Bar = { "test", "test2" }
    };
}

But this doesn't work?

List<string> test = { "test", "test2" };

I do know that i can write new { "test", "test2" }; and then it works. I don't want a workaround or a solution or something similar for this question, instead I'd like to have an explaination what the difference in those two cases exactly is.

EDIT: When changing the types from List<string> to string[] the first example doesn't compile anymore but the 2nd one does.

Thanks!

Dominik
  • 1,623
  • 11
  • 27
  • Possible duplicate of [I don't understand why we need the 'new' keyword](https://stackoverflow.com/questions/38099520/i-dont-understand-why-we-need-the-new-keyword) – CodingYoshi Aug 24 '18 at 22:48
  • I read the question and it's not very clear. You seem to be asking about array initializers but your examples are lists, not arrays. Regardless, you can never allocate a new instance of anything without the `new` keyword, which your second example lacks. – John Wu Aug 24 '18 at 22:48
  • @CodingYoshi it is not even remotely close to being duplicate of that question. – Selman Genç Aug 24 '18 at 22:49
  • @John Wu, Yes I do understand what you say. But why, does the first example work? I don't use the new keyword there either? Please don't teach me about objects and etc. I am software dev since a couple of years. I know that stuff – Dominik Aug 24 '18 at 22:50
  • Yes you do `var test2 = new Foo` it's the 4th symbol. – John Wu Aug 24 '18 at 22:51
  • @JohnWu This is not about the new Foo, it's about the new List inside of the Foo. I am writing Bar = { "test" }; without compiler errors, which should create a new instance of a list too, right? Same I am doing in the 2nd code-snippet where the compiler fails. – Dominik Aug 24 '18 at 22:51
  • 2
    It's called a nested object initializer. See [this question](https://stackoverflow.com/questions/15784483/assignment-to-readonly-property-in-initializer-list). I agree the syntax is counterintuitive. – John Wu Aug 24 '18 at 22:53
  • Did you look at the error message for the code that does not work `Can only use array initializer expressions to assign to array types. Try using a new expression instead.` – Nkosi Aug 24 '18 at 22:56
  • In c# you cannot assign anything to a reference type unless you create an instance of it first. That's why you cannot do this: `List test = { "test", "test2" };` – CodingYoshi Aug 24 '18 at 22:56
  • 1
    @JohnWu The funny thing is: When i execute the first code snipped it crashes with a nullreferenceexception because the list is null and it seems that the add method is called for each value in the list i provide – Dominik Aug 24 '18 at 22:56
  • @Nkosi I still don't really understand what the difference between the examples is. When i initialize the list in the first snipped it's a list too, not an array – Dominik Aug 24 '18 at 22:58
  • @Dominik change the Bar property to string array and look at the error produced. Then change the test variable to a string array as well and see how it works. While the two syntax look exactly the same they mean two completely different things based on where there are used. – Nkosi Aug 24 '18 at 22:59
  • @Nkosi After that I am even more confused. When i change the type from List to array in both snippets the 2nd snippet works and the first one doesn't work anymore. – Dominik Aug 24 '18 at 23:01
  • @Dominik read the error messages. They tell you exactly why they do not work – Nkosi Aug 24 '18 at 23:02
  • @Nkosi an object of type string [] cannot be initialised with an enumerable initialiser. Yea right... but why does it work for one of the two snippets. They are both string arrays??? – Dominik Aug 24 '18 at 23:03
  • @Dominik when that syntax is used in a nested object intilaizer the expression is not interpreted as an array intializer expression but as an object initializer or as a collection initializer. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers – Nkosi Aug 24 '18 at 23:06
  • 1
    @Nkosi Ahh ok, so when i use {"test"} in a "normal" scope then it's interpreted as an array initializer (which creates a new instance of an array) and when i use it inside of an object initializer it's interpreted as collection initializer and therefore calls the Add-Method of the collection instead of creating a new instance of it - did I get this right? – Dominik Aug 24 '18 at 23:08
  • 1
    @Dominik now you're getting it. Yes. – Nkosi Aug 24 '18 at 23:10
  • @Nkosi Ok, thank you, post it as an answer and I'll accept it. IMHO... the syntax is very strange in that case – Dominik Aug 24 '18 at 23:11

1 Answers1

4

The confusion here comes from the same syntax being treated as different in different contexts. In the object initializer, the expression represents a collection initializer.

The spec defines collection initializer as:

7.6.10.3 Collection initializers

A collection initializer consists of a sequence of element initializers, enclosed by { and } tokens and separated by commas. Each element initializer specifies an element to be added to the collection object being initialized, and consists of a list of expressions enclosed by { and } tokens and separated by commas.

Later in the same chapter:

For each specified element in order, the collection initializer invokes an Add method on the target object with the expression list of the element initializer as argument list, applying normal overload resolution for each invocation.

So this is why you get NRE in the first example. Since you don't add a new List<string> before using the collection initializer, you are attempting to call Add on a null object. You will either need to add new List<string> or do it in the constructor.

An array initializer is different, when you omit new there the compiler just adds new for you, so this:

string[] values = { "foo", "bar" };

is equivalent to:

string[] values = new [] { "foo", "bar" };

When you use { "foo", "bar" } outside of the object initializer you get the error because you are trying to assign an array to a list.

Note: In the collection initializer, you might expect the compiler to be smarter and add the new for you as in the array initializer but it might have other side effects. For example, if you are initializing the list in the constructor, it would overwrite the list. So I guess that's why it's the way it is.

Selman Genç
  • 100,147
  • 13
  • 119
  • 184