131

Today I was surprised to find that in C# I can do:

List<int> a = new List<int> { 1, 2, 3 };

Why can I do this? What constructor is called? How can I do this with my own classes? I know that this is the way to initialize arrays but arrays are language items and Lists are simple objects ...

Lloyd
  • 8,204
  • 2
  • 38
  • 53
Ignacio Soler Garcia
  • 21,122
  • 31
  • 128
  • 207
  • 1
    This question may be of help: http://stackoverflow.com/questions/1744967/in-c-is-there-a-way-to-write-custom-object-initializers-for-new-data-types – Bob Kaufman Jan 13 '12 at 16:38
  • 10
    Pretty awesome huh? You can do very similar code to initialize dictionaries as well: `{ { "key1", "value1"}, { "key2", "value2"} }` – danludwig Jan 13 '12 at 16:40
  • From another view this array like initialization syntax gives us a reminder that underlying data type of a `List` is actually an array only. I hate C# team for the fact that they not name it `ArrayList` which sounds so obvious and natural. – RBT Mar 21 '17 at 08:59

6 Answers6

183

This is part of the collection initializer syntax in .NET. You can use this syntax on any collection you create as long as:

  • It implements IEnumerable (preferably IEnumerable<T>)

  • It has a method named Add(...)

What happens is the default constructor is called, and then Add(...) is called for each member of the initializer.

Thus, these two blocks are roughly identical:

List<int> a = new List<int> { 1, 2, 3 };

And

List<int> temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
List<int> a = temp;

You can call an alternate constructor if you want, for example to prevent over-sizing the List<T> during growing, etc:

// Notice, calls the List constructor that takes an int arg
// for initial capacity, then Add()'s three items.
List<int> a = new List<int>(3) { 1, 2, 3, }

Note that the Add() method need not take a single item, for example the Add() method for Dictionary<TKey, TValue> takes two items:

var grades = new Dictionary<string, int>
    {
        { "Suzy", 100 },
        { "David", 98 },
        { "Karen", 73 }
    };

Is roughly identical to:

var temp = new Dictionary<string, int>();
temp.Add("Suzy", 100);
temp.Add("David", 98);
temp.Add("Karen", 73);
var grades = temp;

So, to add this to your own class, all you need do, as mentioned, is implement IEnumerable (again, preferably IEnumerable<T>) and create one or more Add() methods:

public class SomeCollection<T> : IEnumerable<T>
{
    // implement Add() methods appropriate for your collection
    public void Add(T item)
    {
        // your add logic    
    }

    // implement your enumerators for IEnumerable<T> (and IEnumerable)
    public IEnumerator<T> GetEnumerator()
    {
        // your implementation
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Then you can use it just like the BCL collections do:

public class MyProgram
{
    private SomeCollection<int> _myCollection = new SomeCollection<int> { 13, 5, 7 };    

    // ...
}

(For more information, see the MSDN)

adjan
  • 13,371
  • 2
  • 31
  • 48
James Michael Hare
  • 37,767
  • 9
  • 73
  • 83
  • 29
    Good answer, though I note that it is not quite accurate to say "exactly identical" in your first example. It is exactly identical to `List temp = new List(); temp.Add(1); ... List a = temp;` That is, the `a` variable is not initialized until *after* all the adds are called. Otherwise it would be legal to do something like `List a = new List() { a.Count, a.Count, a.Count };` which is a crazy thing to do. – Eric Lippert Jan 13 '12 at 17:50
  • @EricLippert, `List a = new List() { a.Count, a.Count, a.Count };` is not legal in either case because don't have a reference to at that point. I think his initial snippet was perfectly fine. – user606723 Jan 13 '12 at 18:38
  • Nevermind, i see now. What your actual concern is this.. `List a; a = new List() {a.Count, a.Count, a.Count};` But this is definitely legal and there is nothing wrong with this because a.Count would get resolved before hand. – user606723 Jan 13 '12 at 18:40
  • 1
    @user606723: There isn't any real difference between `List a; a = new List() { a.Count };` and `List a = new List() { a.Count };` – Joren Jan 13 '12 at 18:51
  • 4
    @Joren: You are correct; in fact the C# specification states that `T x = y;` is the same as `T x; x = y;`, This fact can lead to some odd situations. For instance, `int x = M(out x) + x;` is perfectly legal because `int x; x = M(out x) + x;` is legal. – Eric Lippert Jan 13 '12 at 19:03
  • 1
    @JamesMichaelHare it's not necessary to implement `IEnumerable`; the non-generic `IEnumerable` is enough to allow the use of collection initializer syntax. – phoog Jan 13 '12 at 19:05
  • @EricLippert and Joren, I stand corrected. That's very odd. I had no idea one could do that. – user606723 Jan 13 '12 at 19:19
  • @user606723: Indeed you can. However, you may not do so and use var at the same time. You can't say `var x = M(out x) + x;` because we can't determine the type of x until we know the type of the initializer, but we can't determine the type of the initializer until overload resolution succeeds on M, and overload resolution cannot succeed until we know the type of x. Rather than try to solve this "chicken and egg" problem we simply give up. – Eric Lippert Jan 13 '12 at 19:48
  • @phoog: You're right, I'm just so used to favoring `IEnumerable` over `IEnumerable`, corrected. – James Michael Hare Jan 13 '12 at 20:42
  • 2
    Eric's point is important if you're using some kind of collection that implements IDisposable. `using (var x = new Something{ 1, 2 })` won't dispose the object if one of the `Add` calls fails. – porges Jan 17 '12 at 20:16
11

It is so called syntactic sugar.

List<T> is the "simple" class, but compiler gives a special treatment to it in order to make your life easier.

This one is so called collection initializer. You need to implement IEnumerable<T> and Add method.

Krizz
  • 11,362
  • 1
  • 30
  • 43
8

According to the C# Version 3.0 Specification "The collection object to which a collection initializer is applied must be of a type that implements System.Collections.Generic.ICollection for exactly one T."

However, this information appears to be inaccurate as of this writing; see Eric Lippert's clarification in the comments below.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 10
    That page is not accurate. Thanks for bringing this to my attention. That must be some preliminary pre-release documentation that somehow never got deleted. The original design was to require ICollection, but that is not the final design we settled on. – Eric Lippert Jan 13 '12 at 17:52
6

Another cool thing about collection initializers is that you can have multiple overloads of Add method and you can call them all in the same initializer! For example this works:

public class MyCollection<T> : IEnumerable<T>
{
    public void Add(T item, int number)
    {

    }
    public void Add(T item, string text) 
    {

    }
    public bool Add(T item) //return type could be anything
    {

    }
}

var myCollection = new MyCollection<bool> 
{
    true,
    { false, 0 },
    { true, "" },
    false
};

It calls the correct overloads. Also, it looks for just the method with name Add, the return type could be anything.

nawfal
  • 70,104
  • 56
  • 326
  • 368
6

It works thanks to collection initializers which basically require the collection to implement an Add method and that will do the work for you.

vc 74
  • 37,131
  • 7
  • 73
  • 89
0

The array like syntax is being turned in a series of Add() calls.

To see this in a much more interesting example, consider the following code in which I do two interesting things that sound first illegal in C#, 1) setting a readonly property, 2) setting a list with a array like initializer.

public class MyClass
{   
    public MyClass()
    {   
        _list = new List<string>();
    }
    private IList<string> _list;
    public IList<string> MyList 
    { 
        get
        { 
            return _list;
        }
    }
}
//In some other method
var sample = new MyClass
{
    MyList = {"a", "b"}
};

This code will work perfectly, although 1) MyList is readonly and 2) I set a list with array initializer.

The reason why this works, is because in code that is part of an object intializer the compiler always turns any {} like syntax to a series of Add() calls which are perfectly legal even on a readonly field.

yoel halb
  • 12,188
  • 3
  • 57
  • 52