Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing two Dictionaries in C#

I have two dictionaries, both with the same structure and order (one is supposed to be an exact replicate of the other): Dictionary<int, ICustomInterface>and I want to check that they are equal using SequenceEqual<>

First, I turn the first dictionary into XML, and then read it back to recreate the second one. Upon initial inspection, they are both the same. The ICustomeInterface objects each override the Equals method properly. To check this, I iterate over the elements of the two dictionaries and compare them. They are all equal.

But when I call the SequenceEqual:dictionary1.SequenceEqual(dictionary2); it returns false and the Equals methods of the ICustomInterface objects never get called and it always returns false. However, if I do this:

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

everything works as expected and it returns true for every line. So, what's going on when I simply call SequnceEqual on the dictionary itself?

like image 907
sbenderli Avatar asked Jul 19 '11 19:07

sbenderli


2 Answers

"What's going on" is it's comparing KeyValuePair entries for the two dictionaries, in order. Dictionaries are inherently unordered - you shouldn't be relying on anything about the order in which entries come out of them. If you use:

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

I suspect you'll find that matches. It's a pretty unpleasant way to compare them though :)

like image 141
Jon Skeet Avatar answered Nov 13 '22 06:11

Jon Skeet


Jon Skeet has already given a good explanation.

However if all you (or anyone else reading this question) want is an efficient method of comparing dictionaries here is a simple Linq-based extension that will do just that:

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

It does perhaps look a bit fluffy, but I wanted good readability (and null tests).

So if all you want is a "one-liner" and you already know that both dictionaries are non-null and that the TValue type overrides the Equals method properly, then you only really need this much (sans the null-checks if TValue is a valuetype of course):

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

If you want to do a compare where the dictionaries doesn't have to have the same type of value, or if you prefer to use a delegate or lambda expression instead of having to implement an IEqualityComparer, this extension will do the trick for you instead:

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

As you can see it's pretty much the same thing as before.

Here is a usage example:

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

If you run the above code isEqual will become true.


Caution: As user LukeSchoen points out in the comments, this method might fail to give the expected result if the dictionaries being tested don't use the same EqualityComparer for comparing keys (remember that you can optionally specify an equality comparer for your keys in the dictionary constructor, e.g. to use case-insensitive string keys).

This cannot be fixed since one cannot generalize what's "expected" from trying to equate two dictionaries that use different definitions of equality.

The only solution in the general case is to let the caller specify their own EqaulityComparer for the inter-dictionary key comparison, similarly to how the code above lets the caller specify how values are compared, thus making it the callers responsibility to provide a sensible definition of equality for their use case.

like image 23
AnorZaken Avatar answered Nov 13 '22 06:11

AnorZaken