2010-10-25 46 views
17

Estoy desarrollando una aplicación en C# targeting .NET 3.5. En él, tengo 2 diccionarios similares que contienen criterios de validación para un conjunto específico de elementos en mi aplicación. Ambos diccionarios tienen firmas idénticas. El primer diccionario tiene la configuración predeterminada y el segundo diccionario contiene algunas configuraciones definidas por el usuario.C# Merging 2 diccionarios

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

Me gustaría combinar los 2 diccionarios en uno que contenga los elementos de ambos diccionarios.

El problema con el que me estoy encontrando es que es posible que ambos diccionarios tengan algunos de los mismos valores de clave. La regla básica que quiero es tener una combinación de ambos diccionarios y si hay alguna clave en la configuración personalizada que ya exista en la configuración predeterminada, el valor de la configuración personalizada sobrescribirá el valor predeterminado de configuración. La mejor solución que tengo es solo un bucle foreach, compruebe si la clave existe en el otro diccionario, y si no, agréguela.

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

He hecho algunas consultas básicas de LINQ, pero todavía estoy trabajando en aprender las cosas más avanzadas. He visto algunas consultas que fusionarán 2 diccionarios, pero la mayoría implica agrupar cualquier elemento con claves duplicadas, o solo devolver una colección con solo las claves duplicadas/¿Hay una consulta o expresión LINQ que imitará el comportamiento del bucle foreach? ¿Estoy usando?

Respuesta

38

dos puntos:

  1. LINQ no es muy grande para la ejecución de efectos secundarios. En este caso, intentas mutar una colección existente en lugar de ejecutar una consulta, por lo que me alejaría de una solución LINQ pura.
  2. El setter on the generic dictionary's indexer ya tiene el efecto de agregar el par clave-valor si la clave no existe, o sobrescribir el valor si lo hace.

Cuando se establece el valor de la propiedad, si la clave está en el diccionario, el valor asociado con esa clave se sustituye por el valor asignado . Si la clave no está en el diccionario , la clave y el valor se agregan al diccionario.

Así que su bucle foreach es esencialmente equivalente a:

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

Ahora que es bastante escueta ya, así que no creo que LINQ va a ayudar a que todo lo que mucho.

+3

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

+1

1 por enseñarme algo que yo no sé. Soy relativamente autodidacta con C# y nunca supe que podrías hacer eso con diccionarios. – psubsee2003

+0

@ psubsee2003: Saludos. Estoy bastante seguro de que utilicé el mismo patrón que tu código antes de descubrir que era redundante (por casualidad, estaba hurgando con un reflector). – Ani

2

Si vas a hacer esto mucho, entonces lo recomiendo escribir un comparador de igualdad para las claves del diccionario:

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

Y a continuación, siempre que lo necesite para combinar los diccionarios, puede hacer lo siguiente

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

esta funcionó para mí, incluso sin la clase/parámetro 'KeyEqualityComparer'. Gracias – AceMark

1

Creo que la respuesta que seleccioné originalmente sigue siendo la mejor respuesta para este caso en particular, me encontré en otra situación similar hace un momento en la que tenía 2 IEnumerable<> objetos que quería convertir a un diccionario y fusionar en de manera similar, así que pensé que agregaría esa solución aquí para ayudar a alguien en el futuro.En lugar de convertir ambos en un diccionario y usar el método en la respuesta seleccionada, encontré un nuevo enfoque.

De hecho, publiqué el initial solution en SE-CodeReview y de hecho tuve una sugerencia para refinarlo aún más. Aquí está el código final utilicé:

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 clave para esto es Foo tendrá prioridad sobre Equals() definir lo Foo objetos pueden ser considerados por duplicado, y los miembros que definen qué objetos son duplicados también deben ser la clave Dictionary<> (en este caso Name)

Si no puede anular Equals() en Foo, a continuación, la otra opción es utilizar Concat() y GroupBy() en lugar de 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

Aquí hay un buen método de extensión basado en la respuesta de 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

Cambiaría Dictionary on IDictionary –

+0

Si se trata de una colección de diccionarios 'IEnumerable >' ¿cómo puede hacerlos coincidir? – barteloma