7

Recently, I came across some code that looked like this:

public class Test
{
    public ICollection<string> Things { get; set; }

    public Test()
    {
        Things = new List<string> { "First" };
    }

    public static Test Factory()
    {
        return new Test
        {
            Things = { "Second" }
        };
    }
}

Calling Test.Factory() results in a Test object with a Things collection containing both "First" and "Second".

It looks like the line Things = { "Second" } calls the Add method of Things. If the ICollection is changed to an IEnumerable, there is a syntax error stating "IEnumerable<string> does not contain a definition for 'Add'".

It is also apparent that you can only use this kind of syntax in an object initialiser. Code such as this is invalid:

var test = new Test();
test.Things = { "Test" };

What is the name of this feature? In which version of C# was it introduced? Why is it only available in object initialisers?

Synoptic
  • 81
  • 4
  • I can bet it was introduced in C# 6.0 but I can't find any information about it. – MistyK Feb 01 '17 at 15:29
  • 2
    It's called [collection initializer](https://msdn.microsoft.com/en-us/library/bb384062.aspx) and I'm pretty sure it was earlier than c#6, but can't tell exactly. – René Vogt Feb 01 '17 at 15:31
  • http://geekswithblogs.net/BlackRabbitCoder/archive/2015/05/08/c.net-little-wonders-indexer-initializer-syntax.aspx - it's C# 6.0 – MistyK Feb 01 '17 at 15:32
  • http://stackoverflow.com/questions/459652/why-do-c-sharp-collection-initializers-work-this-way – René Vogt Feb 01 '17 at 15:32
  • http://stackoverflow.com/questions/2495791/custom-collection-initializers – Dmitry Bychenko Feb 01 '17 at 15:32
  • I didn't think it was a collection initialiser because that msdn page on the subject (and every other source I've found) doesn't demonstrate the syntax `Things = { "Second" }`. In every example, the first `{` is preceded by the `new` keyword to denote a completely new object. – Synoptic Feb 01 '17 at 15:36
  • 1
    It works with my VS2010, so it was there with C#3 already. Changing my language-version from C#3 to ISO-2 lead to many compiler-errors: `Feature 'collection initializer' cannot be used because it is not part of the ISO-2 C# language specification` so it was added on C# 3. – MakePeaceGreatAgain Feb 01 '17 at 15:36
  • 2
    @Synoptic Then you're looking at object initializers, not collection initializers. – Servy Feb 01 '17 at 15:41
  • @HimBromBeere What about if you change `Things = new List { "First" };` to `Things = new List();`? – Synoptic Feb 01 '17 at 15:47
  • 2
    So in this case the `=` is 'adding' not 'setting'? I don't like it - very confusing. – PeteGO Feb 01 '17 at 15:47

2 Answers2

5

It is called a collection initializer and it was added in the C# 3 language specifications (section 7.5.10.3 at introduction, section 7.6.10.3 in the current specs). To be specific, the code you use uses an embedded collection initializer.

Collection initializer actually just call the Add method, which is required according to the specs.

As Quantic commented, the specs say:

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.

That explains your unexpected results quite good.

Why is it only available in object initialisers?

Because it doesn't make sense elsewhere. You could just call the Add method yourself instead of using initializers for something else than initializing.

silkfire
  • 24,585
  • 15
  • 82
  • 105
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Just for completeness, the section number of the current spec (version 5.0) to look at is 7.6.10.3 – Matthew Watson Feb 01 '17 at 16:05
  • 1
    The syntax `Things = { "Second" }` is more specifically an "embedded collection initializer", as said here in the spec: "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 closest example of this syntax I've seen is in the same section of the spec but for an "nested object initializer": `P1 = { X = 0, Y = 1 }`. – Quantic Feb 01 '17 at 16:37
  • Thank you @Quantic that's exactly what I was looking for – Synoptic Feb 01 '17 at 16:47
1

As Patrick already mentioned the collection initializer sequentially calls Add on the list. This assumes your property has been initialized by a constructor accordingly:

public class MyClass
{
    public List<MyType> TheList { get; private set; }
    public MyClass() 
    {
        this.TheList = new List<MyType>(); 
    }
}

If there is no such constructor that initializes your list you'll get a NullReferenceException in the following statement:

test.Things = { "Test" }; 

However this is something different than the following:

var m = new MyClass { TheList = new List<MyType> { ... } };

in which case you'll access the properties setter. Having none (or only a private one as in my example) leads to a compiler-error.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111