How does dictionary initialization work in C#?

Other people have already pointed out how the C# compiler emits calls to the Add method of a Dictionary. However, no one has still talked about when the C# compiler emits those calls.

Well, it has nothing to do with the System.Collections.Dictionary (or System.Collections.Generic.Dictionary<TKey, TValue>) class. This feature is called collection initialization, as it requires the target instance to be of a type that implements the System.Collections.IEnumerable interface. The only requirement more is the presence of a method named Add, not even its signature nor its return type are ever considered.

When the Add method expects one only argument, initialization looks like this:

class Foo : IEnumerable
{
    public void Add(string a)
    {

    }

    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

var myFoo = new Foo
{
    "item 1",
    "item 2",
    "item 3",
    "item 4",
    "item 5"
};

The above code represents exactly what happens with the System.Collections.List (or System.Collections.Generic.List<T>) class, as it implements System.Collections.IEnumerable.

When the Add method expects two arguments or more, like it is for a dictionary, then every item must have values for each parameter of that method, so it was designed to have those values within braces and looks like this:

class Foo : IEnumerable
{
    public void Add(string a, int b, bool c)
    {

    }

    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

var myFoo = new Foo
{
    { "item 1", 3, false },
    { "item 2", 1, true },
    { "item 3", 4, true },
    { "item 4", 1, true },
    { "item 5", 5, true },
};
For curious people:

The code in this answer includes a funny thing that is related to circles. Will anyone ever discover it?


Yes, compiler uses default parameterless constructor and then adds all values specified in collection initializer via Dictionary.Add method. As Jon pointed, your code is compiled into

Dictionary<int, double> maxDictionary2;
Dictionary<int, double> maxDictionary;

maxDictionary2 = new Dictionary<int, double>();
maxDictionary2.Add(10, 40000.0);
maxDictionary = maxDictionary2;

Generated IL:

.maxstack 3
.locals init (
     [0] class [mscorlib]Dictionary`2<int32, float64> maxDictionary,
     [1] class [mscorlib]Dictionary`2<int32, float64> maxDictionary2)
L_0000: nop 
L_0001: newobj instance void [mscorlib]Dictionary`2<int32, float64>::.ctor()
L_0006: stloc.1 
L_0007: ldloc.1 
L_0008: ldc.i4.s 10
L_000a: ldc.r8 40000
L_0013: callvirt instance void [mscorlib]Dictionary`2<int32, float64>::Add(!0, !1)
L_0018: nop 
L_0019: ldloc.1 
L_001a: stloc.0 

I.e. created dictionary assigned to temporary variable maxDictionary2, filled with values, and only then reference to created and filled dictionary is copied to maxDictionary variable.

Keep in mind that you can specify any other constructor, if you don't want to use parammeterless one. E.g. you can use one which sets initial capacity:

var maxDictionary = new Dictionary<int, double>(10) { { 10, 40000 } };

var maxDictionary = new Dictionary<int, double> { { 10, 40000 } };

Here is the generated IL of the program

IL_0001:  newobj      System.Collections.Generic.Dictionary<System.Int32,System.Double>..ctor
IL_0006:  stloc.1     // <>g__initLocal0
IL_0007:  ldloc.1     // <>g__initLocal0
IL_0008:  ldc.i4.s    0A 
IL_000A:  ldc.r8      00 00 00 00 00 88 E3 40 
IL_0013:  callvirt    System.Collections.Generic.Dictionary<System.Int32,System.Double>.Add
IL_0018:  nop         
IL_0019:  ldloc.1     // <>g__initLocal0
IL_001A:  stloc.0     // maxDictionary

Clearly it uses parameterless constructor and calls Add method. Label "IL_0013" shows call to Add method

Equivalent c# code would be

Dictionary<int, double> maxDictionary;
Dictionary<int, double> temp = new Dictionary<int, double>();
temp.Add(10, 40000.0);
maxDictionary = temp;

Worth noting that compiler uses temp variable, I can see two reasons for that

  1. To make sure you don't get half baked dictionary when it encounters an exception.
  2. You don't expect the compiler to read the field for just creating a new instance and assigning. Isn't it?