Different Ways of Initializing a Dictionary in .NET
August 31, 2022I was today years old when I found out that you can initialize .NET Dictionary
s
like this.
var dict = new Dictionary<int, string>
{
[23] = "foo",
[42] = "bar"
};
The way I was used to doing that was like this:
var dict = new Dictionary<int, string>
{
{ 23, "foo"},
{ 23, "bar"}
};
So, what's the difference there?
Let's fire up a .NET disassembler (like ildasm
) and look at the generated IL code.
Note: I you are using Mac or Linux, there is AvaloniaILSpy for decompiling .NET assemblies using a GUI tool.
The first version (using the [ x ] = y
notation) produces following code.
.method private hidebysig static
void '<Main>$' (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Header size: 1
// Code size: 33 (0x21)
.maxstack 8
.entrypoint
IL_0000: newobj instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, string>::.ctor()
IL_0005: dup
IL_0006: ldc.i4.s 23
IL_0008: ldstr "foo"
IL_000d: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, string>::set_Item(!0, !1)
IL_0012: nop
IL_0013: ldc.i4.s 42
IL_0015: ldstr "bar"
IL_001a: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, string>::set_Item(!0, !1)
IL_001f: nop
IL_0020: ret
} // end of method Program::'<Main>$'
Without the syntactic sugar of the initializer, this decompiles to:
Dictionary<int, string> dictionary = new Dictionary<int, string>();
dictionary[23] = "foo";
dictionary[42] = "bar";
So, we are basically using the set_Item
indexer to add a value to the dictionary.
On the other hand, by using the initializer syntax I have been used to ({ x, y }
),
we get following IL code.
.method private hidebysig static
void '<Main>$' (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Header size: 1
// Code size: 33 (0x21)
.maxstack 8
.entrypoint
IL_0000: newobj instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, string>::.ctor()
IL_0005: dup
IL_0006: ldc.i4.s 23
IL_0008: ldstr "foo"
IL_000d: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, string>::Add(!0, !1)
IL_0012: nop
IL_0013: ldc.i4.s 42
IL_0015: ldstr "bar"
IL_001a: callvirt instance void class [System.Collections]System.Collections.Generic.Dictionary`2<int32, string>::Add(!0, !1)
IL_001f: nop
IL_0020: ret
} // end of method Program::'<Main>$'
Which would decompile to this (without using the initializer syntactic sugar);
Dictionary<int, string> dictionary = new Dictionary<int, string>();
dictionary.Add(23, "foo");
dictionary.Add(42, "bar");
This means, the Dictionary
s Add
method gets used here.
Note the difference?
This means, with the bracket initializer syntax, supplying duplicate keys would totally work and the last added value wins.
var dict = new Dictionary<int, string>
{
[23] = "foo",
[42] = "bar"
[23] = "BAZ!", // This is fine.
};
Whereas using this initializer syntax, duplicate keys result in a
System.ArgumentException: An item with the same key has already been added.
error.
var dict = new Dictionary<int, string>
{
{ 23, "foo"},
{ 42, "bar"}
{ 23, "BAZ!"} // This results in an exception.
};
To be honest, I never ran into this situation yet where this would be relevant but at least I learned something new.