Mario Vernari
Mario Vernari

Reputation: 7304

Why the nested type must be explicitly declared?

I think this is the first time I'm using the new C#6 dictionary-initialization feature, and I need a nested structure like the below one:

        var map = new Dictionary<string, Dictionary<string, object>>()
        {
            ["WNodeScramblingResetZone_Unlock"] = 
            {
                ["updater"] ="pinco",
                ["rdlev"] = 55
            },
            ["WNodeScramblingResetZone_NewPwd"] = 
            {
                ["updater"] ="pallo",
                ["wrlev"] = 75,
                ["is_online"] = true
            }
        };

This syntax seems fine: no compilation errors. However, when the program starts I see a couple of "KeyNotFoundException" errors in the output window, and the application does not work.

By the way, if I explicitly declare the nested type instantiation, everything runs fine.

        var map = new Dictionary<string, Dictionary<string, object>>()
        {
            ["WNodeScramblingResetZone_Unlock"] = new Dictionary<string, object>()
            {
                ["updater"] ="pinco",
                ["rdlev"] = 55
            },
            ["WNodeScramblingResetZone_NewPwd"] = new Dictionary<string, object>()
            {
                ["updater"] ="pallo",
                ["wrlev"] = 75,
                ["is_online"] = true
            }
        };

Is that a limitation, a bug, or rather there is a concrete reason to define it?

Upvotes: 4

Views: 192

Answers (2)

csharpfolk
csharpfolk

Reputation: 4280

I used http://tryroslyn.azurewebsites.net/ to translate this C# code into VB.NET:

Public Class Program
    Public Shared Sub Main()
        Dim expr_06 As Dictionary(Of String, Dictionary(Of String, Object)) = New Dictionary(Of String, Dictionary(Of String, Object))()
        expr_06("x")("updater") = "pinco"
        expr_06("x")("rdlev") = 55
        expr_06("y")("updater") = "pallo"
        expr_06("y")("wrlev") = 75
        expr_06("y")("is_online") = True
    End Sub
End Class

It cleary shows that new dictionaries are not created only accessed by key. The new [key] = value syntax compiles to dictionary[key] = value not like old one {key, value} which compiled to dictionary.Add(key, value).

I think this is a feature, compiler doesn't know what type of IDictionary you need so you must provide concrete type. There is also posibility that you use some dynamic collection that automatically creates subdictionnaries on first access in that case this code would work. As you may see compiler leaves you great freedom to do what you want at the expense of program verbosity.

EDIT: I write a bit lengthy blog post about initializers that provides a more deep explanation

Upvotes: 1

svick
svick

Reputation: 244797

When you write code like this:

var map = new Dictionary<string, Dictionary<string, object>>()
{
    ["WNodeScramblingResetZone_Unlock"] = 
    {
        ["updater"] ="pinco"
    }
};

It's compiled into the equivalent of this:

var map = new Dictionary<string, Dictionary<string, object>>()
map["WNodeScramblingResetZone_Unlock"]["updater"] = "pinco";

It does not create any instances that you didn't explicitly specify.

This is code that could work (which is why it's allowed) for some dictionary-like class, which returns empty collection for keys that don't exist, but will indeed cause KeyNotFoundException for Dictionary.

On the other hand, this code:

var map = new Dictionary<string, Dictionary<string, object>>()
{
    ["WNodeScramblingResetZone_Unlock"] = new Dictionary<string, object>()
    {
        ["updater"] ="pinco"
    }
};

Is compiled as:

var map = new Dictionary<string, Dictionary<string, object>>();
var tmp = new Dictionary<string, object>();
tmp["updater"] = "pinco";
map["WNodeScramblingResetZone_Unlock"] = tmp;

As you can see, this calls the map["WNodeScramblingResetZone_Unlock"] setter with a new instance (instead of the getter in the previous version), which is why it works.

Upvotes: 3

Related Questions