W jaki sposób inicjalizacja słownika C # jest poprawna?

64

Natknąłem się na następujące i zastanawiam się, dlaczego nie spowodowało to błędu składniowego.

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
        }
    }
};

Zauważ, że między „MyA” i „OtherAs” brakuje „,”.

W tym miejscu dochodzi do zamieszania:

  1. Kod się kompiluje.
  2. Ostatni słownik „dict” zawiera tylko trzy elementy: „Id”, „Tribes” i „MyA”.
  3. Wartości dla wszystkich oprócz „MyA” są poprawne,
  4. „MyA” przyjmuje zadeklarowaną wartość dla „OtherAs”, a jej pierwotna wartość jest ignorowana.

Dlaczego to nie jest nielegalne? Czy to z założenia?

Dantte
źródło
1
@ Steve o to pyta. I wklejona to pod sharplab i wygląda na to, początkowo OtherAszostanie dodany jako klucz do tego, co byłobyMyA słownika. (Name = Solo, Points = 88 plus OtherAs = List <Dictionary <string, object >>), ale nigdy nie przypisuje go . Zamiast tego umieszcza listę nagrań, teraz zawierającą tylko jeden wpis Punkty = 1999 w MyAgnieździe, zastępując to, co według nich należy.
pinkfloydx33
1
Link był zbyt długi, aby wkleić go w pierwszym komentarzu. To naprawdę dziwne. sharplab.io/…
pinkfloydx33
1
Tak, przetestowałem to z LinqPad i uzyskałem ten sam wynik. Nie jestem pewien, co się tutaj dzieje. Zobaczmy, czy jakiś guru C # mógłby tu rzucić trochę światła.
Steve
1
Jeśli zmienisz pierwszy słownik na <string, string> and modify Points to „88”, pojawi się błąd kompilatora „Nie można niejawnie przekonwertować typu„ System.Collections.Generic.List <System.Collections.Generic.Dictionary <ciąg, obiekt >> ”na„ ciąg ” co faktycznie pomogło mi znaleźć odpowiedź!
pinkfloydx33
2
@OlegI Nie sądzę, że jest to błąd kompilatora. Wiele dobrych wyjaśnień już zawartych w odpowiedziach. Ale jeśli spróbujesz var test = new Dictionary<string, object> { ["Name"] = "Solo", ["Points"] = 88 }["OtherAs"] = new List<Dictionary<string, object>> { new Dictionary<string, object> { ["Points"] = 1999 } };, wyjaśnię to dalej.
MKR

Odpowiedzi:

54

Brakujący przecinek robi różnicę. Powoduje to zastosowanie indeksu ["OtherAs"]do tego słownika:

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

Więc zasadniczo mówisz:

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

Zauważ, że jest to wyrażenie przypisania ( x = y). Oto xsłownik z „Nazwa” i „Punkty”, indeksowany za pomocą "OtherAs"i yjest List<Dictionary<string, object>>. Wyrażenie przypisania ocenia na wartość przypisywaną ( y), która jest listą słowników.

Wynik tego całego wyrażenia jest następnie przypisywany do klawisza „MyA”, dlatego „MyA” ma listę słowników.

Możesz potwierdzić, że tak się dzieje, zmieniając typ słownika 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
    }
}

Oto twój kod, ale został ponownie sformatowany i dodano kilka nawiasów ilustrujących, w jaki sposób kompilator go przeanalizował:

["MyA"] 
= 
(
    (
        new Dictionary<string, object>
        {
            ["Name"] = "Solo",
            ["Points"] = 88
        }["OtherAs"] 
    )
    = 
    (
        new List<Dictionary<string, object>>
        {
            new Dictionary<string, object>
            {
                ["Points"] = 1999
            }
        }
    )
)
Zamiatarka
źródło
1
Zmieniał typ wartości słownika z object na, stringco dało mi podobny komunikat o błędzie i moment aha, który wywołał odpowiedź. Wygląda na to, że mnie pobiłaś
pinkfloydx33
19

To, co się tutaj dzieje, polega na tym, że tworzysz słownik, a następnie indeksujesz go. Wynik wyrażenia indeksującego / przypisania jest następnie zwracany i to właśnie jest przypisywane do MyAszczeliny słownika.

To:

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

Można podzielić na następujący kod psuedo:

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

Zwracany jest wynik przypisania do indeksatora drugiego słownika (przypisanie zwraca wartość przypisaną / prawą stronę). Ten słownik jest tymczasowym lokalnym, który po prostu „znika w eterze”. Wynik indeksu (lista słowników) znajduje się na końcu w słowniku głównym.

To dziwny przypadek, do którego łatwiej jest wpaść ze względu na użycie objectjako typu wartości słownika.

pinkfloydx33
źródło