intrigued_66
intrigued_66

Reputation: 17230

Comparing two dictionaries for equal data in c#?

I have two dictionaries containing a string key and then an object. The object contains five fields. Is there an elegant way to ensure both dictionaries first contain the same keys and then if this is correct, contain the same five fields per object?

Would the two dictionaries have the same in-built hashcode or something?

EDIT, doesn't appear to be working for the following code:

Dictionary<string, MyClass> test1 = new Dictionary<string, MyClass>();
Dictionary<string, MyClass> test2 = new Dictionary<string, MyClass>();

MyClass i = new MyClass("", "", 1, 1, 1, 1);
MyClass j = new MyClass("", "", 1, 1, 1, 1);

test1.Add("1", i);
test2.Add("1", j);

bool equal = test1.OrderBy(r => r.Key).SequenceEqual(test2.OrderBy(r => r.Key));

class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a= aa;
        b= bb;
        c= cc;
        d= dd;
        e= ee;
        f= ff;
    }

this returns false?

Upvotes: 23

Views: 36386

Answers (5)

greyseal96
greyseal96

Reputation: 890

There were a couple of answers on here that I think got pretty close but there were a couple of additional points that I thought should be added so I'm adding them as another possible answer.

First, I would avoid using the SequenceEquals method. It is an extension method for Enumerable and implicitly requires the two collections to be in the same order. Dictionaries are not meant to be ordered collections so using SequenceEquals means that you'll have to needlessly iterate over both of the dictionaries to create sorted/ordered intermediary collections that you also don't need and then iterate over those collections to compare them for equality. That seems really inefficient and an abuse of LINQ, all in the name of trying to be terse and write a one line solution. If the OP's idea of "elegant" is terse, I guess this will do the trick, but it seems wasteful.

On the other hand, if the OP's idea of "elegant" is efficient, then you'll probably need to write a little more code. First, you should either override the Equals method for your class or implement IEquatable in your class (see here, for example). This will allow you to compare the values in the dictionary. Then, you'll probably want to do something like implement an interface like IEqualityComparer for your dictionary.

Then, the comparison of the two dictionaries would go something like below. It's just a quick and dirty "back of the napkin" example so it's not an example of the best way to do it, but it's meant to illustrate a way to iterate only as many times over the dictionary as necessary and quit out as soon as an inequality is found.

First the required code:

public class Foo
{
    //members here...
    public override bool Equals(object obj)
    {
        //implementation here
    }
    //You should probably also override GetHashCode to be thorough,
    //but that's an implementation detail...
}

//This method could stand on its own or you could change it to make it 
//part of the implementation of one of the comparison interfaces...
bool DictionariesEqual(Dictionary<K, V> x, Dictionary<K, V> y)
{
    //If we're comparing the same object, it's obviously equal to itself.
    if(x == y)
    {
        return true;
    }
    //Make sure that we don't have null objects because those are
    //definitely not equal.
    if (x == null || y == null)
    {
        return false;
    }
    
    //Stop processing if at any point the dictionaries aren't equal.
    bool result = false;

    //Make sure that the dictionaries have the same count.
    result = x.Count == y.Count;
    
    //If we passed that check, keep going.
    if(result)
    {
        foreach(KeyValuePair<K, V> xKvp in x)
        {
            //If we don't have a key from one in the other, even though
            //the counts are the same, the dictionaries aren't equal so
            //we can fail out.
            V yValue;
            if(!y.TryGetValue(xKvp.Key, out yValue))
            {
                result = false;
                break;
            }
            else
            {
                //Use the override of the Equals method for your object
                //to see if the value from y is equal to the value from
                //x.
                result = xKvp.Value.Equals(yValue);
                if(!result)
                {
                    //If they're not equal we can just quit out.
                    break;
                }
            }
        }
    }
    return result;
}

Then we'd use it like this:

Dictionary<string, Foo> dict1 = new Dictionary<string, Foo>();
Dictionary<string, Foo> dict2 = new Dictionary<string, Foo>();
//Fill the dictionaries here...

//Compare the dictionaries
bool areDictsEqual = DictionariesEqual(dict1, dict2);

So, it's not the most terse code, but it's also not iterating more than necessary. In my opinion, that's more elegant.

Edit: A commenter suggested that the code examples get updated to make them generic. I originally just went off of the example of the original poster but making this generic seemed like a good idea with one caveat: Be careful what the type of the key is. If it's a simple type like a string or an int that C# includes equality implementations for, everything should be good. If the key is a custom type, you'll need to make sure that you override Equals() and GetHashCode() for your custom type.

Upvotes: 1

Philipp
Philipp

Reputation: 659

In this case you can just use the SequenceEquals()-Method, like following:

   Dictionary<string, object> d1 = new Dictionary<string, object>();
   d1.Add("first", new { Name = "TestName", Age = 12, ID = 001 }); 

   Dictionary<string, object> d2 = new Dictionary<string, object>();
   d2.Add("first", new { Name = "TestName", Age = 12, ID = 001 });

   Console.WriteLine(d1.SequenceEqual(d2)); //outputs True                

Note: For simplicity i used implicit classes to fill the Dictionaries. The code will work the same way with any objects. The hashcodes of both dictionaries are not equal, which can be easily verified by doing the following:

   Console.WriteLine(d1.GetHashCode() + " " + d2.GetHashCode()); //outputs different hashcodes

Upvotes: 0

Habib
Habib

Reputation: 223207

First you have to override Equals and GetHashCode method in your class, otherwise comparison will be performed on references instead of actual values. (The code to override Equals and GetHashCode is provided at the end), after that you can use:

var result = (dic1 == dic2) || //Reference comparison (if both points to same object)
             (dic1.Count == dic2.Count && !dic1.Except(dic2).Any());

Since the order in which items in Dictionary are returned is undefined, you can not rely on Dictionary.SequenceEqual (without OrderBy).

You can try:

Dictionary<string, object> dic1 = new Dictionary<string, object>();
Dictionary<string, object> dic2 = new Dictionary<string, object>();
dic1.Add("Key1", new { Name = "abc", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic1.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });

dic2.Add("Key1",new { Name = "abc",Number=  "123", Address= "def", Loc="xyz"});
dic2.Add("Key2", new { Name = "DEF", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key3", new { Name = "GHI", Number = "123", Address = "def", Loc = "xyz" });
dic2.Add("Key4", new { Name = "JKL", Number = "123", Address = "def", Loc = "xyz" });


bool result = dic1.SequenceEqual(dic2); //Do not use that

Most of the time the above will return true, but one can't really rely on that due to unordered nature of Dictionary.

Since SequenceEqual will compare the order as well, therefore relying on only SequenceEqual could be wrong. You have to use OrderBy to order both dictionaries and then use SequenceEqual like:

bool result2 = dic1.OrderBy(r=>r.Key).SequenceEqual(dic2.OrderBy(r=>r.Key));

But that will involve multiple iterations, once for ordering and the other for comparing each element using SequenceEqual.

Code for overriding Equals and GetHashCode

private class MyClass
{
    private string a;
    private string b;
    private long? c;
    private decimal d;
    private decimal e;
    private decimal f;

    public MyClass(string aa, string bb, long? cc, decimal dd, decimal ee, decimal ff)
    {
        a = aa;
        b = bb;
        c = cc;
        d = dd;
        e = ee;
        f = ff;
    }

    protected bool Equals(MyClass other)
    {
        return string.Equals(a, other.a) && string.Equals(b, other.b) && c == other.c && e == other.e && d == other.d && f == other.f;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((MyClass)obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = (a != null ? a.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ (b != null ? b.GetHashCode() : 0);
            hashCode = (hashCode * 397) ^ c.GetHashCode();
            hashCode = (hashCode * 397) ^ e.GetHashCode();
            hashCode = (hashCode * 397) ^ d.GetHashCode();
            hashCode = (hashCode * 397) ^ f.GetHashCode();
            return hashCode;
        }
    }
}

You may also see: Correct way to override Equals() and GetHashCode()

Upvotes: 23

Rawling
Rawling

Reputation: 50104

You can use

bool dictionariesEqual = 
    dic1.Keys.Count == dic2.Keys.Count &&
    dic1.Keys.All(k => dic2.ContainsKey(k) && object.Equals(dic2[k], dic1[k]));

Upvotes: 30

Sebastian Negraszus
Sebastian Negraszus

Reputation: 12195

The built-in Equals function of Dictionary<T> only checks for reference equality, see this question on SO. Hashcodes do not reliably tell you if two objects are equal; there is always a chance of hash collision. Never use hashcodes as an equality test!

I would do it by hand: Compare the entry count of both dictionaries, iterate over the key-value-pairs of one dictionary and check if the key exists in the other one and compare the corresponding objects from both dictionaries. Edit: See Rawling's answer :)

Upvotes: 1

Related Questions