Is there a difference between using square brackets and curly braces in dictionary initializers?

Viewed 756

In dictionary initializers, there are two ways to create the dictionary with content:

new Dictionary<string, GradientSpace>
{
    ["userSpaceOnUse"] = GradientSpace.Absolute,
    ["objectBoundingBox"] = GradientSpace.Relative
})

and

new Dictionary<string, GradientSpace>
{
    {"userSpaceOnUse", GradientSpace.Absolute},
    {"objectBoundingBox", GradientSpace.Relative}
});

Is there any difference at all in these two syntaxes, or is it just a matter of preference?

2 Answers

When the source is as follows and the target framework is .NET 4.7.1:

var x = new Dictionary<string, int>
{
    ["userSpaceOnUse"] = 1,
    ["objectBoundingBox"] = 3
};

var y = new Dictionary<string, int> {
    {"userSpaceOnUse", 1},
    {"objectBoundingBox", 3}
};

This results in the following Intermediate Language (using JetBrains dotPeek):

// [18 13 - 22 15]
IL_0001: newobj       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::.ctor()
IL_0006: dup          
IL_0007: ldstr        "userSpaceOnUse"
IL_000c: ldc.i4.1     
IL_000d: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::set_Item(!0/*string*/, !1/*int32*/)
IL_0012: nop          
IL_0013: dup          
IL_0014: ldstr        "objectBoundingBox"
IL_0019: ldc.i4.3     
IL_001a: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::set_Item(!0/*string*/, !1/*int32*/)
IL_001f: nop          
IL_0020: stloc.0      // x

// [25 13 - 28 15]
IL_0021: newobj       instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::.ctor()
IL_0026: dup          
IL_0027: ldstr        "userSpaceOnUse"
IL_002c: ldc.i4.1     
IL_002d: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0/*string*/, !1/*int32*/)
IL_0032: nop          
IL_0033: dup          
IL_0034: ldstr        "objectBoundingBox"
IL_0039: ldc.i4.3     
IL_003a: callvirt     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::Add(!0/*string*/, !1/*int32*/)
IL_003f: nop          
IL_0040: stloc.1      // y

The first way results in the indexer/property being set, while the second way uses the Add() method, meaning they are translated differently.

The .NET Core source of the Dictionary class is also interesting to look at in this context: https://github.com/Microsoft/referencesource/blob/master/mscorlib/system/collections/generic/dictionary.cs

The (newer) syntax with [ … ] = … is required to use the set accessor of the indexer, so you have:

tmpDict["userSpaceOnUse"] = GradientSpace.Absolute;
tmpDict["objectBoundingBox"] = GradientSpace.Relative;

while the other syntax is required to use a suitable method with the name Add, so:

tmpDict.Add("userSpaceOnUse", GradientSpace.Absolute);
tmpDict.Add("objectBoundingBox, GradientSpace.Relative);

This difference is not an arbitrary C# compiler choice; it is required by the C# Language Specification.

For the particular type System.Collections.Generic.Dictionary<,>, there is a difference between the two members (indexer setter vs. Add method) when the key is already present. So if you (presumably) accidentally include the same key twice, as in:

new Dictionary<string, GradientSpace>
{
  ["userSpaceOnUse"] = GradientSpace.Absolute,
  ["objectBoundingBox"] = GradientSpace.Relative,
  ["userSpaceOnUse"] = GradientSpace.Relative,
}

respectively:

new Dictionary<string, GradientSpace>
{
  { "userSpaceOnUse", GradientSpace.Absolute },
  { "objectBoundingBox", GradientSpace.Relative },
  { "userSpaceOnUse", GradientSpace.Relative },
}

you will feel the difference! In the first case the last use of the key "userSpaceOnUse" will just overwrite the first value (the first line with "userSpaceOnUse" becomes irrelevant), while in the last case it will blow up with a DuplicateKeyException when run.

Related