2011-07-19 25 views
8

Tengo dos diccionarios, ambos con la misma estructura y el orden (uno se supone que es una réplica exacta de la otra): Dictionary<int, ICustomInterface> y quiero comprobar que son iguales usando SequenceEqual<>La comparación de dos Diccionarios en C#

Primero, convierto el primer diccionario en XML y luego lo leo para recrear el segundo. En la inspección inicial, ambos son iguales. Los objetos ICustomeInterface anulan el método Equals correctamente. Para comprobar esto, iterar sobre los elementos de los dos diccionarios y compararlos. Todos son iguales.

Pero cuando llamo al SequenceEqual: dictionary1.SequenceEqual(dictionary2);, devuelve falso y los métodos de igualdad de los objetos ICustomInterface nunca se llaman y siempre devuelve falso. Sin embargo, si hago esto:

for (i = 0; i < firstDictionary.Count; i++) 
    firstDictionary[i].SequenceEqual(otherSub.ItemSequence[i]); 

todo funciona como se espera y se cumple para cada línea. Entonces, ¿qué sucede cuando simplemente llamo a SequnceEqual en el diccionario mismo?

+2

"Los objetos ICustomeInterface anulan el método Equals correctamente". ¿Eso incluye una anulación constante para 'GetHashCode()'?No estoy seguro si es relevante aquí, pero un 'GetHashCode()' roto es uno de los problemas más comunes con las comparaciones de igualdad. – CodesInChaos

+0

¿Cuál es el tipo subyacente? ¿Es un tipo de valor o un tipo de referencia? – Tipx

+1

Tienes una contradicción en tu publicación. Usted declara que tiene "dos diccionarios ordenados" pero, por otro lado, declara que usa 'Dictionary ', que está desordenado. – CodesInChaos

Respuesta

15

"Lo que está pasando" es que está comparando las entradas KeyValuePair para los dos diccionarios, en orden. Los diccionarios son inherentemente desordenados; no debe confiar en nada sobre el orden en que las entradas salen de ellos. Si usa:

firstDictionary.OrderBy(pair => pair.Key) 
       .SequenceEqual(secondDictionary.OrderBy(pair => pair.Key)) 

Sospecho que encontrará esas coincidencias. Sin embargo, es una forma bastante desagradable de compararlos :)

+4

¡Pronto tendrá más insignias que puntos de reputación! –

+0

No se pudo implementar un IEqualityComparer, por lo que la comparación no es solo una comparación de referencia –

+0

@Conrad: Sí, podría ... pero luego se topa de inmediato con el problema de ordenar, por lo que no ayuda mucho. –

1

Jon Skeet ya dio una buena explicación.

Sin embargo, si todo lo que (o cualquier otra persona que lee esta pregunta) quieren es un método eficaz para la comparación de los diccionarios que aquí hay una simple extensión a base de LINQ que va a hacer precisamente eso:

/// <summary> 
/// Compares two dictionaries for equality. 
/// </summary> 
/// <returns> 
/// True if the dictionaries have equal contents or are both null, otherwise false. 
/// </returns> 
public static bool DictionaryEqual<TKey, TValue>(
    this IDictionary<TKey, TValue> dict1, IDictionary<TKey, TValue> dict2, 
    IEqualityComparer<TValue> equalityComparer = null) 
{ 
    if (dict1 == dict2) 
     return true; 

    if (dict1 == null | dict2 == null) 
     return false; 

    if (dict1.Count != dict2.Count) 
     return false; 

    if (equalityComparer == null) 
     equalityComparer = EqualityComparer<TValue>.Default; 

    return dict1.All(kvp => 
     { 
      TValue value2; 
      return dict2.TryGetValue(kvp.Key, out value2) 
       && equalityComparer.Equals(kvp.Value, value2); 
     }); 
} 

lo hace tal vez parecen un Un poco esponjoso, pero quería buena legibilidad (y pruebas nulas).

Así que si lo que quieres es un "one-liner" y ustedes ya saben que los dos diccionarios son no nulo y que el tipo TValue anula el método Equals correctamente, entonces sólo se necesita realmente esta cantidad (sans las nulos comprueba si TValue es un valuetype por supuesto):

bool isEqual = dict1.Count == dict2.Count && dict1.All(kvp => 
    { 
     TValue value2; 
     return dict2.TryGetValue(kvp.Key, out value2) 
      && (kvp.Value == null ? value2 == null : kvp.Value.Equals(value2)); 
    }); 

Si usted quiere hacer una comparación en los diccionarios no tiene por qué tener el mismo tipo de valor, o si prefiere utilizar una expresión delegada o lambda en lugar de tener que implementar un IEqualityComparer, esta extensión En su lugar, hará el truco para usted:

/// <summary> 
/// Compares two dictionaries for equality using a custom value equality function. 
/// </summary> 
/// <returns> 
/// True if both dictionaries are null or both have the same set of keys and comparing 
/// their respective values for each key using the <paramref name="valueEqualityFunc"/> 
/// returns true, otherwise false. 
/// </returns> 
public static bool DictionaryEqual<TKey, TValue1, TValue2>(
    this IDictionary<TKey, TValue1> dict1, IDictionary<TKey, TValue2> dict2, 
    Func<TValue1, TValue2, bool> valueEqualityFunc) 
{ 
    if (valueEqualityFunc == null) 
     throw new ArgumentNullException("valueEqualityFunc"); 

    if (dict1 == dict2) 
     return true; 

    if (dict1 == null | dict2 == null) 
     return false; 

    if (dict1.Count != dict2.Count) 
     return false; 

    return dict1.All(kvp => 
    { 
     TValue2 value2; 
     return dict2.TryGetValue(kvp.Key, out value2) 
      && valueEqualityFunc(kvp.Value, value2); 
    }); 
} 

Como puede ver, es casi lo mismo que antes.

Aquí está un ejemplo de uso:

var d1 = new Dictionary<string, string>(); 
var d2 = new Dictionary<string, string>(); 

d1.Add("key1", "dog"); 
d2.Add("key1", "Dog"); 
d1.Add("key2", "CAT"); 
d2.Add("key2", "cat"); 

bool isEqual = DictionaryEqual(d1, d2, 
    (s1, s2) => string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase)); 

Si ejecuta el código anterior isEqual llegará a ser verdad.