2013-04-24 27 views
25

Często znajduję się przy tworzeniu Dictionary z nietrywialną klasą wartości (np. List), a następnie zawsze zapisuję ten sam wzór kodu podczas wypełniania danych.Słownik .NET: pobierz lub utwórz nowy

Na przykład:

var dict = new Dictionary<string, List<string>>(); 
string key = "foo"; 
string aValueForKey = "bar"; 

Oznacza to, że chcę wstawić "bar" na listę, która odpowiada kluczowym "foo", gdzie klucz "foo" nie może być przypisane do niczego.

To gdzie używam stale powtarzający się wzór:

List<string> keyValues; 
if (!dict.TryGetValue(key, out keyValues)) 
    dict.Add(key, keyValues = new List<string>()); 
keyValues.Add(aValueForKey); 

Czy istnieje bardziej elegancki sposób to zrobić?

Podobne pytania, które nie mają odpowiedzi na to pytanie:

+0

Co jeśli klucz istnieje, ale lista ma wartość zerową? –

+3

@Barabba Ogólnie rzecz biorąc sądzę, że dodanie wartości pustej byłoby niewłaściwe, a użytkownik * spodziewałby się *, że kod po prostu zbankrutuje i naprawi błąd dodania pustego klucza, nie próbując go obsłużyć tutaj. – Servy

+1

@Servy ok, ale co, jeśli otrzymamy listę wyników z zewnętrznego źródła? Jedyną rzeczą, która sobie z tym poradzi, to radzić sobie z wartościami zerowymi, czy jestem w błędzie? Zdarza mi się to każdego dnia :) –

Odpowiedz

30

Mamy nieco inne zdanie na ten temat, ale efekt jest podobne:

public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key) 
    where TValue : new() 
{ 
    TValue val; 

    if (!dict.TryGetValue(key, out val)) 
    { 
     val = new TValue(); 
     dict.Add(key, val); 
    } 

    return val; 
} 

Called:

var dictionary = new Dictionary<string, List<int>>(); 

List<int> numbers = dictionary.GetOrCreate("key"); 

To sprawia, że ​​korzystanie z ogólnym ograniczeniem dla konstruktorów bez parametrów publicznych: where TValue : new().

Aby pomóc w odkryciu, chyba że metoda rozszerzenie jest dość specyficzny do wąskiego problemu, mamy tendencję, aby umieścić metody rozszerzenie w obszarze nazw typu są przedłużające, w tym przypadku:

namespace System.Collections.Generic 

Większość czas, osoba używająca tego typu ma zdefiniowaną na górze instrukcję using, więc IntelliSense znalazłby również metody jego rozszerzenia zdefiniowane w kodzie.

+1

@ Darthenius Nie, pierwsze podejście jest specyficzne dla list (mamy inne dla ' HashSet' i inne elementy 'Dictionary <>'). Nie pamiętam dokładnego rozumowania, ale myślę, że ponieważ 'List <>' również zawiera typ ogólny, robi to w ten sposób gra ładniej z wnioskiem typu. –

+2

@ Darthenius Cóż, właśnie wróciłem do naszego kodu i skomentowałem wersję 'List <>', która już nie ma zastosowania, była funkcjonalnie taka sama jak standardowa powyżej. Więc usunąłem to. To musiało zostać przez przypadek, ponieważ kodowałem to za pierwszym razem. –

+1

@ Darthenius Cóż, nie były one dokładnie funkcjonalnie równoważne, więc ich nie usunąłem. Jednak pod względem tego pytania nie miało to znaczenia. Różnili się tym, że sprawdzili, czy element jest pusty, a nie element nie znajdujący się w słowniku, więc klucz z pustym elementem również go utworzył. –

3

Jak z tak wielu problemów programowania, gdy znajdziesz się robić coś dużo, byłaby go w metodzie:

public static void MyAdd<TKey, TCollection, TValue>(
    this Dictionary<TKey, TCollection> dictionary, TKey key, TValue value) 
    where TCollection : ICollection<TValue>, new() 
{ 
    TCollection collection; 
    if (!dictionary.TryGetValue(key, out collection)) 
    { 
     collection = new TCollection(); 
     dictionary.Add(key, collection); 
    } 
    collection.Add(value); 
} 
+0

@ Darthenius To tylko inne podejście. Zawęża zakres metody do dodawania elementów do dowolnej kolekcji implementującej "ICollection". Głównym celem jest umieszczenie kodu w oddzielnej lokalizacji do ponownego użycia. Nie sądzę, że próbował ulepszyć już dostarczony kod. –

+2

@ Darthenius Możesz również zwrócić kolekcję, jeśli chcesz, zakładając, że jest to szczególnie powszechne zadanie. W moich doświadczeniach podczas używania wzorca 'Dictionary ' Często nie dodaję wielu wartości dla tego samego klucza naraz. Jeśli jest to coś, co się często zdarza, możesz dodać kolejne przeciążenie, w którym akceptujesz 'IEnumerable ' i po prostu dodajesz je wszystkie do metody. – Servy

0

Ok, inne podejście:

public static bool TryAddValue<TKey,TValue>(this System.Collections.Generic.IDictionary<TKey,List<TValue>> dictionary, TKey key, TValue value) 
    { 
     // Null check (useful or not, depending on your null checking approach) 
     if (value == null) 
      return false; 

     List<TValue> tempValue = default(List<TValue>); 

     try 
     { 
      if (!dictionary.TryGetValue(key, out tempValue)) 
      { 
       dictionary.Add(key, tempValue = new List<TValue>()); 
      } 
      else 
      { 
       // Double null check (useful or not, depending on your null checking approach) 
       if (tempValue == null) 
       { 
        dictionary[key] = (tempValue = new List<TValue>()); 
       } 
      } 

      tempValue.Add(value); 
      return true; 
     } 
     catch 
     { 
      return false; 
     } 
    } 

W ten sposób masz aby "spróbować dodać" twoją wartość do ogólnej listy (oczywiście generalizowanej do ogólnej kolekcji), zerowej kontroli i próby uzyskania istniejących kluczy/wartości w twoim słowniku. Użycie i przykład:

var x = new Dictionary<string,List<string>>(); 
x.TryAddValue("test", null); // return false due to null value. Doesn't add the key 
x.TryAddValue("test", "ok"); // it works adding the key/value 
x.TryAddValue("test", "ok again"); // it works adding the value to the existing list 

Mam nadzieję, że to pomaga.

0

A co z tym?

var keyValues = dictionary[key] = dictionary.ContainsKey(key) ? dictionary[key] : new List<string>(); 
keyValues.Add(aValueForKey);