Comparing two dictionaries for equal data in c#?

前端 未结 5 1517
小蘑菇
小蘑菇 2021-02-07 09:13

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 k

相关标签:
5条回答
  • 2021-02-07 09:41

    You can use

    bool dictionariesEqual = 
        dic1.Keys.Count == dic2.Keys.Count &&
        dic1.Keys.All(k => dic2.ContainsKey(k) && object.Equals(dic2[k], dic1[k]));
    
    0 讨论(0)
  • 2021-02-07 09:52

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

    0 讨论(0)
  • 2021-02-07 09:54

    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
    
    0 讨论(0)
  • 2021-02-07 09:58

    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<string, Foo> x, Dictionary<string, Foo> 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<string, Foo> 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.
                Foo 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.

    0 讨论(0)
  • 2021-02-07 10:00

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

    0 讨论(0)
提交回复
热议问题