2013-04-24 8 views
25

Spesso mi trovo a creare un Dictionary con una classe di valore non banale (ad esempio List) e quindi a scrivere sempre lo stesso modello di codice quando compili i dati.Dizionario .NET: ottieni o crea nuovo

Ad esempio:

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

Cioè, voglio inserire "bar" nella lista che corrisponde alla chiave "foo", dove la chiave "foo" potrebbero non essere mappati a nulla.

Questo è dove io uso il modello sempre ripetere:

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

C'è un modo più elegante di fare questo?

domande

correlati che non hanno risposte a questa domanda:

+0

Cosa succede se la chiave esiste ma l'elenco è nullo? –

+3

@Barabba In generale, penso che l'aggiunta di un valore nullo sarebbe considerata inappropriata e ci si aspetterebbe * che il codice bombardi semplicemente e corregge l'errore di aggiungere una chiave nulla, non provando a gestirlo qui. – Servy

+1

@Servy ok, ma cosa succede se otteniamo un risultato Elenco da una fonte di terze parti? L'unica cosa da affrontare è gestire valori nulli, sbaglio? Mi capita tutti i giorni :) –

risposta

30

Abbiamo una leggermente diversa su questo, ma l'effetto è simile:

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

Chiamato:

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

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

si avvale del vincolo generico per costruttori senza parametri pubblici: where TValue : new().

per aiutare con la scoperta, a meno che il metodo di estensione è abbastanza specifica a un problema stretto, si tende a mettere metodi di estensione nello spazio dei nomi del tipo stanno ampliando, in questo caso:

namespace System.Collections.Generic 

La maggior parte delle l'ora, la persona che utilizza il tipo ha l'istruzione using definita nella parte superiore, quindi IntelliSense troverà anche i metodi di estensione definiti nel codice.

+1

@Darthenius No, il primo approccio è specifico per gli elenchi (ne abbiamo altri per ' HashSet' e altri elementi 'Dictionary <>'). Non riesco a ricordare il ragionamento esatto, ma penso che anche la 'Lista <>' contiene un tipo generico, in questo modo è più piacevole con l'inferenza di tipo. –

+2

@Darthenius Bene, sono appena tornato al nostro codice e ho commentato la versione di 'List <>' che avevamo e non ha più un utilizzo, era funzionalmente uguale a quella standard sopra. Quindi l'ho rimosso. Deve essere stato lasciato indietro per caso mentre stavo codificandolo la prima volta. –

+1

@Darthenius Beh, non erano esattamente equivalenti dal punto di vista funzionale, quindi non li ho rimossi. Tuttavia, in termini di questa domanda non era rilevante. Differivano nel fatto che controllassero che un oggetto fosse nullo, non un oggetto che non fosse nel dizionario, quindi una chiave con un oggetto nullo ne creerebbe anche una. –

3

Come con tanti problemi di programmazione, quando vi trovate a fare qualcosa di molto, il refactoring in un metodo:

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 È solo una presa diversa. Riduce lo scopo del metodo fino all'aggiunta di elementi a qualsiasi raccolta che implementa 'ICollection'. Il punto principale è quello di ospitare il codice in una posizione separata per il riutilizzo. Non penso che stesse tentando di migliorare il codice già fornito. –

+2

@Darthenius Puoi anche restituire la raccolta se lo desideri, assumendo che sia una cosa particolarmente comune da fare. Nelle mie esperienze con l'uso del pattern di 'Dictionary ' Non aggiungo spesso più valori per lo stesso tasto tutto in una volta. Se è qualcosa che accade spesso potresti aggiungere un altro overload in cui accetti un 'IEnumerable ' e aggiungili tutti all'interno del metodo. – Servy

0

Ok, approccio diverso:

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

In questo modo si ha per "provare ad aggiungere" il tuo valore a un elenco generico di (ovviamente generalizzabile a una raccolta generica), a un controllo nullo e cercando di ottenere la chiave/i valori esistenti nel tuo dizionario. Uso e l'esempio:

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 

Speranza che aiuta.

0

E che dire di questo?

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