2010-10-25 12 views
17

Sto sviluppando un'applicazione in C# con targeting per .NET 3.5. In esso, ho 2 dizionari simili che contengono criteri di convalida per un insieme specifico di elementi nella mia app. Entrambi i dizionari hanno firme identiche. Il primo dizionario ha le impostazioni predefinite e il secondo dizionario contiene alcune impostazioni definite dall'utente.C# Merging 2 dizionari

var default_settings = new Dictionary<string, MyElementSettings>(); 
var custom_settings = new Dictionary<string, MyElementSettings>(); 

Vorrei unire i 2 dizionari in uno che contiene gli elementi di entrambi i dizionari.

Il problema che sto incontrando è che entrambi i dizionari hanno gli stessi valori chiave. La regola base che voglio è avere una combinazione di entrambi i dizionari e se ci sono chiavi nelle custom_settings che esistono già in default_settings, il valore custom_settings sovrascriverà il valore default_settings. La soluzione migliore che ho è solo un ciclo foreach, controlla se la chiave esiste nell'altro dizionario e, in caso contrario, aggiungila.

foreach (var item in custom_settings) 
{ 
    if (default_settings.ContainsKey(item.Key)) 
     default_settings[item.Key] = item.Value; 
    else 
     default_settings.Add(item.Key, item.Value); 
} 

Ho effettuato alcune query LINQ di base, ma sto ancora lavorando all'apprendimento delle cose più avanzate. Ho visto alcune query che fonderanno 2 dizionari, ma la maggior parte riguarda il raggruppamento di qualsiasi elemento con chiavi duplicate, o restituisce solo una raccolta con solo le chiavi duplicate/Esiste una query o un'espressione LINQ che imiterà il comportamento del ciclo foreach Sto usando?

risposta

38

due punti:

  1. LINQ non è grande per l'esecuzione di effetti collaterali. In questo caso, stai tentando di mutare una raccolta esistente piuttosto che eseguire una query, quindi eviterei una soluzione LINQ pura.
  2. Lo setter on the generic dictionary's indexer ha già l'effetto di aggiungere la coppia chiave-valore se la chiave non esiste o sovrascrive il valore se lo fa.

Quando si imposta il valore della proprietà, se la chiave è nel dizionario, il valore associato alla quella chiave è sostituito dal valore assegnato. Se la chiave non si trova nel dizionario , la chiave e il valore vengono aggiunti al dizionario.

Così il vostro ciclo foreach è sostanzialmente equivalente a:

foreach (var item in custom_settings) 
{ 
    default_settings[item.Key] = item.Value; 
} 

Ora che è abbastanza tersa già, quindi non credo che LINQ sta per aiutarvi più di tanto.

+3

+1 per il punto # 2 (http://msdn.microsoft.com/en-us/library/k7z0zy8k.aspx) – Brad

+1

+1 per avermi insegnato qualcosa che non sapevo. Sono relativamente autodidatta con C# e non ho mai saputo che potevi farlo con i dizionari. – psubsee2003

+0

@ psubsee2003: Saluti. Sono abbastanza sicuro di aver usato lo stesso schema del tuo codice prima di scoprire che era ridondante (per caso, mi sono trovato in giro con il riflettore). – Ani

2

Se la vostra intenzione di fare questo molto, allora vi consiglio di scrivere un confronto di uguaglianza per le chiavi del dizionario:

private class KeyEqualityComparer<T, U> : IEqualityComparer<KeyValuePair<T, U>> 
{ 
    public bool Equals(KeyValuePair<T, U> x, KeyValuePair<T, U> y) 
    { 
     return x.Key.Equals(y.Key); 
    } 

    public int GetHashCode(KeyValuePair<T, U> obj) 
    { 
     return obj.Key.GetHashCode(); 
    } 
} 

E poi ogni volta che è necessario unire dizionari, è possibile effettuare le seguenti operazioni

var comparer = new KeyEqualityComparer<string, MyElementSettings>(); 
dict1 = dict1.Union(dict2,comparer).ToDictionary(a => a.Key, b => b.Value); 
+0

questo ha funzionato per me, anche senza la classe/parametro 'KeyEqualityComparer'. Grazie – AceMark

1

Penso che la risposta che ho selezionato in origine sia ancora la migliore risposta per questo caso particolare, mi sono ritrovato in un'altra situazione simile poco fa quando avevo 2 IEnumerable<> oggetti che volevo convertire in un dizionario e unire insieme in in modo simile, quindi ho pensato di aggiungere questa soluzione per aiutare qualcuno in futuro.Piuttosto che convertire entrambi in un dizionario e utilizzare il metodo nella risposta selezionata, ho trovato un nuovo approccio.

In realtà ho pubblicato il initial solution su SE-CodeReview e in realtà avevo un suggerimento per perfezionare ulteriormente. Ecco il codice finale che ho usato:

public Dictionary<String, Foo> Merge(XElement element1, XElement element2) 
{ 
    IEnumerable<Foo> firstFoos = GetXmlData(element1); // parse 1st set from XML 
    IEnumerable<Foo> secondFoos = GetXmlData(element2); // parse 2nd set from XML 

    var result = firstFoos.Union(secondFoos).ToDictionary(k=>k.Name, v=>v); 

    return result; 
} 

public class Foo 
{ 
    public String Name { get; } 

    // other Properties and Methods 
    // . 
    // . 
    // . 

    public override Boolean Equals(Object obj) 
    { 
     if (obj is Foo) 
     { 
      return this.Name == ((Foo)obj).Name;    
     } 

     return false; 
    } 
} 

La chiave di questo è Foo deve avere la priorità Equals() per definire cosa Foo oggetti possono essere considerati duplicato, ei membri che definiscono quali oggetti sono duplicati dovrebbero anche essere la chiave Dictionary<> (in questo caso Name)

Se non è possibile ignorare Equals() in Foo, poi l'altra opzione è quella di utilizzare Concat() e GroupBy() invece di Union()

public Dictionary<String, Foo> Merge(XElement element1, XElement element2) 
{ 
    IEnumerable<Foo> firstFoos = GetXmlData(element1); // parse 1st set from XML 
    IEnumerable<Foo> secondFoos = GetXmlData(element2); // parse 2nd set from XML 

    var result = firstFoos.Concat(secondFoos) 
          .GroupBy(foo => foo.Name) 
          .Select(grp => grp.First()) 
          .ToDictionary(k=>k.Name, v=>v); 

    return result; 
} 
8

Ecco un bel metodo di estensione basato sulla risposta di Ani.

public static class DictionaryExtensionMethods 
{ 
    public static void Merge<TKey, TValue>(this Dictionary<TKey, TValue> me, Dictionary<TKey, TValue> merge) 
    { 
     foreach (var item in merge) 
     { 
      me[item.Key] = item.Value; 
     } 
    } 
} 
+1

Vorrei cambiare Dizionario su IDictionary –

+0

Se questa è una raccolta di dizionari 'IEnumerable >' come possono corrisponderli? – barteloma