64

I stumbled upon the following and I'm wondering why it didn't raise a syntax error.

var dict = new Dictionary<string, object>
{
    ["Id"] = Guid.NewGuid(),
    ["Tribes"] = new List<int> { 4, 5 },
    ["MyA"] = new Dictionary<string, object>
    {
        ["Name"] = "Solo",
        ["Points"] = 88
    }
    ["OtherAs"] = new List<Dictionary<string, object>>
    {
        new Dictionary<string, object>
        {
            ["Points"] = 1999
        }
    }
};

Notice that the "," is missing between the "MyA", and "OtherAs".

This is where the confusion happens:

  1. The code compiles.
  2. The final dictionary "dict" contains only three elements: "Id", "Tribes", and "MyA".
  3. The value for all except "MyA" are correct,
  4. "MyA" takes the declared value for "OtherAs", while its original value is ignored.

Why isn't this illegal? Is this by design?

9
  • 1
    @Steve that's what he asking about. I pasted this into sharplab and it looks like initially OtherAs gets added as a key into what would be the MyA dictionary. (Name=Solo, Points=88 plus OtherAs=List<Dictionary <string, object>>) except it then never assigns it. Instead it places the list of dicts, now only containing the single Points=1999 entry into the MyA slot overriding what one would think belongs there. Commented Feb 2, 2020 at 10:17
  • 1
    Link was too long to paste in first comment. This is strange indeed. sharplab.io/… Commented Feb 2, 2020 at 10:18
  • 1
    Yes, I have tested it with LinqPad and got the same result. Not sure what's going on here. Let's see if some C# guru could shed some light here. Commented Feb 2, 2020 at 10:20
  • 1
    If you change the first dictionary to <string, string> and modify Points to "88"` you then get a compiler error "Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object>>' to 'string'" which actually helped me figure out the answer! Commented Feb 2, 2020 at 10:23
  • 2
    @OlegI I dont think its a compiler bug. Many good explanations already provided in answers. But if you try var test = new Dictionary<string, object> { ["Name"] = "Solo", ["Points"] = 88 }["OtherAs"] = new List<Dictionary<string, object>> { new Dictionary<string, object> { ["Points"] = 1999 } }; will explain it further. Commented Feb 2, 2020 at 13:42

2 Answers 2

55

The missing comma makes all the difference. It causes the indexer ["OtherAs"] to be applied on this dictionary:

new Dictionary<string, object>
{
    ["Name"] = "Solo",
    ["Points"] = 88
}

So essentially you're saying:

new Dictionary<string, object>
{
    ["Name"] = "Solo",
    ["Points"] = 88
}["OtherAs"] = new List<Dictionary<string, object>>
{
    new Dictionary<string, object>
    {
        ["Points"] = 1999
    }
};

Note that this is an assignment expression (x = y). Here x is dictionary with "Name" and "Points", indexed with "OtherAs" and y is the List<Dictionary<string, object>>. An assignment expression evaluates to the value being assigned (y), which is the list of dictionaries.

The result of this whole expression is then assigned to the key "MyA", which is why "MyA" has the list of dictionaries.

You can confirm that this is what's happening by changing the type of the dictionary x:

new Dictionary<int, object>
{
    [1] = "Solo",
    [2] = 88
}
// compiler error saying "can't convert string to int"
// so indeed this indexer is applied to the previous dictionary
["OtherAs"] = new List<Dictionary<string, object>>
{
    new Dictionary<string, object>
    {
        ["Points"] = 1999
    }
}

Here is your code, but reformatted and some parentheses added to illustrated how the compiler has parsed it:

["MyA"] 
= 
(
    (
        new Dictionary<string, object>
        {
            ["Name"] = "Solo",
            ["Points"] = 88
        }["OtherAs"] 
    )
    = 
    (
        new List<Dictionary<string, object>>
        {
            new Dictionary<string, object>
            {
                ["Points"] = 1999
            }
        }
    )
)
Sign up to request clarification or add additional context in comments.

1 Comment

It was changing the dictionary's value type from object to string that gave me a similar error message and the aha moment that sparked the answer. Seems you beat me to it--I blame typing the answer on my phone :-p
20

What's happening here is that you are creating a dictionary and then indexing into it. The result of the indexer/assignment expression is then returned and that is what is getting assigned into the MyA dictionary slot.

This:

["MyA"] = new Dictionary<string, string> 
{
   ["Name"] = "Solo",
   ["Points"] = "88" 
}
["OtherAs"] = new List<Dictionary<string, object>>
{
   new Dictionary<string, object>
   {
       ["Points"] = 1999
   }
}

Can be split out into the following psuedo-code:

var temp = new Dictionary<string, object>
{ 
   ["Name"] = "Solo", 
   ["Points"] = 88 
};
// indexed contains result of assignment
var indexed = temp["OtherAs"] = new List<Dictionary<string, object>>
{
   new Dictionary<string, object>
   {
      ["Points"] = 1999
   }
};
// value is set to result of assignment from previous step
["MyA"] = indexed;
// temp is discarded

The result of assigning to the indexer of the second dictionary is returned (the assignment returns the value assigned/right hand side) That dictionary is a temporary local that just "disappears into the ether". The result of the indexer (the list of dictionaries) is what is placed into the main dictionary in the end.

This is a weird case, which is made easier to fall into due to the use of object as the type of the dictionary values.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.