5

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?

laptou
  • 6,389
  • 2
  • 28
  • 59
  • 1
    in the second case, you have to press the shift key more. – TheGeneral Sep 02 '18 at 21:15
  • 1
    Maybe this gives some clarifications: https://stackoverflow.com/questions/28076127/c6s-new-collection-initializer-clarification – Ted Chirvasiu Sep 02 '18 at 21:18
  • @TedChirvasiu That's insightful, but doesn't really answer my question, which is wondering whether there is any difference in what the compiler generates. – laptou Sep 02 '18 at 21:22
  • 1
    Do you mean literally the IL code? Don't quote me on this one, but since the top one from your example uses the indexer and the other uses an Add method, it seems it produces different IL code. You can test this by going to https://dotnetfiddle.net/, going to the dropdown where it says "Tidy up" and click on "View IL". One appears to use "set_Item" the other "Add". Use something like https://text-compare.com/ to spot the differences easily. – Ted Chirvasiu Sep 02 '18 at 21:35

2 Answers2

7

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

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Bouke
  • 1,531
  • 1
  • 12
  • 21
3

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.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181