如何按值比较两个字典,即使键或值是引用类型?

How to compare two dictionaries by value, even if the key or value are reference types?

我想比较两个 Dictionary<SomeClass, List<AnotherClass>>。无论 KeyValuePair 的顺序如何,都应比较字典。在评论中,建议对值进行排序然后使用 SequenceEquals,但我不确定如何 Sort 字典(另外,即使对 lists 进行排序也有帮助,这是不可能的,因为据我了解,因为不能保证列表的通用类型是 IComparable).

当我尝试使用 Equals 方法时,我总是得到 false,因为它会检查 List 是否引用相等。我想让它检查 Lists 的值是否相等。如何实现?

例如,假设我有以下词典:

var dictionary1 = new Dictionary<Day, List<WorkSession>>
{
    { Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } },
    { Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
    { Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } }
};

var dictionary2 = new Dictionary<Day, List<WorkSession>>
{
    { Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } },
    { Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
    { Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } }
};

工作会议:

class WorkSession : IEquatable<WorkSession>
{
    public string Entrance { get; private set; }
    public string Exit { get; private set; }

    public WorkSession(string entrance, string exit)
    {
        Entrance = entrance;
        Exit = exit;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as WorkSession);
    }
    public bool Equals(WorkSession other)
    {
        return other != null &&
               Entrance == other.Entrance &&
               Exit == other.Exit;
    }
    public override int GetHashCode()
    {
        var hashCode = 1257807568;
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Entrance);
        hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Exit);
        return hashCode;
    }

    public static bool operator ==(WorkSession session1, WorkSession session2)
    {
        return EqualityComparer<WorkSession>.Default.Equals(session1, session2);
    }
    public static bool operator !=(WorkSession session1, WorkSession session2)
    {
        return !(session1 == session2);
    }
}

我要两个比较这些字典,结果应该是True。我怎样才能做到这一点?

这里有一些粗略的内容可以帮助您入门。您需要考虑一些极端情况并相应地调整代码。

    class Program
    {
        static void Main(string[] args)
        {
            var x = new Dictionary<SomeClass, List<AnotherClass>>();
            var y = new Dictionary<SomeClass, List<AnotherClass>>();

            x.Add(new SomeClass { SomeNumericProperty = 1 }, new List<AnotherClass> { new AnotherClass { SomeStringProperty = "1" } });
            y.Add(new SomeClass { SomeNumericProperty = 1 }, new List<AnotherClass> { new AnotherClass { SomeStringProperty = "1" } });

            var w = new MyCustomComparer();
            var z = w.Equals(x, y);
        }
    }

    public class MyCustomComparer : IEqualityComparer<Dictionary<SomeClass, List<AnotherClass>>>
    {
        public bool Equals(Dictionary<SomeClass, List<AnotherClass>> x, Dictionary<SomeClass, List<AnotherClass>> y)
        {
            var keysAreEqual = x.Keys.OrderBy(o => o.GetHashCode()).SequenceEqual(y.Keys.OrderBy(o => o.GetHashCode()));
            var valuesAreEqual = x.SelectMany(o => o.Value).OrderBy(o => o.GetHashCode()).SequenceEqual(y.SelectMany(o => o.Value).OrderBy(o => o.GetHashCode()));

            return keysAreEqual && valuesAreEqual;
        }

        public int GetHashCode(Dictionary<SomeClass, List<AnotherClass>> obj)
        {
            throw new NotImplementedException();
        }
    }

    public class AnotherClass
    {
        protected bool Equals(AnotherClass other)
        {
            return string.Equals(SomeStringProperty, other.SomeStringProperty);
        }

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

        public override int GetHashCode()
        {
            int hash = 13;
            hash = (hash * 7) + SomeStringProperty.GetHashCode();
            return hash;
        }

        public string SomeStringProperty { get; set; }
    }

    public class SomeClass
    {
        public int SomeNumericProperty { get; set; }

        protected bool Equals(SomeClass other)
        {
            return SomeNumericProperty == other.SomeNumericProperty;
        }

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

        public override int GetHashCode()
        {
            int hash = 13;
            hash = (hash * 7) + SomeNumericProperty.GetHashCode();
            return hash;
        }
    }

下面是一个可能的解决方案。与其他一些答案相比,平等检查略有收紧。例如,添加了一些 null 检查,并以不同的方式检查值(基本上不仅检查 Values 是否相同,而且对于给定的 它们是否相同键).

此外,在比较列表时,数据按 WorkSession 的所有属性排序 - 以防两个不同的 WorkSession 值具有相同的哈希码。 更好的长期解决方案是 WorkSession 实施 IComparable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace MattConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new Dictionary<Day, List<WorkSession>>
            {
                { Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } },
                { Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
                { Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } }
            };

            var y = new Dictionary<Day, List<WorkSession>>
            {
                { Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } },
                { Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
                { Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } }
            };
            var w = new MyCustomComparer();
            var shouldBeTrue = w.Equals(x, y);

            Console.WriteLine(shouldBeTrue);

            x[Day.Wednesday] = new List<WorkSession>() { new WorkSession("10:00", "00:00") };
            x[Day.Thursday] = new List<WorkSession>() { new WorkSession("10:01", "00:01") };
            y[Day.Thursday] = new List<WorkSession>() { new WorkSession("10:00", "00:00") };
            y[Day.Wednesday] = new List<WorkSession>() { new WorkSession("10:01", "00:01") };

            var shouldBeFalse = w.Equals(x, y);

            Console.WriteLine(shouldBeFalse);

            Console.ReadLine();
        }
    }

    public class MyCustomComparer : IEqualityComparer<Dictionary<Day, List<WorkSession>>>
    {
        public bool Equals(Dictionary<Day, List<WorkSession>> x, Dictionary<Day, List<WorkSession>> y)
        {
            if (ReferenceEquals(x, null))
                return ReferenceEquals(y, null);

            if (ReferenceEquals(y, null))
                return false;

            if (x.Count != y.Count)
                return false;

            if (!x.Keys.OrderBy(z => z).SequenceEqual(y.Keys.OrderBy(z => z)))
                return false;

            foreach (var kvp in x)
            {
                List<WorkSession> matching;
                if (y.TryGetValue(kvp.Key, out matching))
                {
                    if (ReferenceEquals(matching, null))
                        return ReferenceEquals(kvp.Value, null);

                    if (ReferenceEquals(kvp.Value, null))
                        return false;

                    // ordering by hash code is not strictly necessary
                    if (
                        !matching.OrderBy(z => z.GetHashCode())
                            .ThenBy(z => z.Entrance).ThenBy(z => z.Exit)
                            .SequenceEqual(
                                kvp.Value.OrderBy(z => z.GetHashCode())
                                .ThenBy(z => z.Entrance).ThenBy(z => z.Exit)))
                        return false;
                }
                else
                    return false;
            }

            return true;
        }

        public int GetHashCode(Dictionary<Day, List<WorkSession>> obj)
        {
            throw new NotImplementedException();
        }
    }

    public class WorkSession : IEquatable<WorkSession>
    {
        public string Entrance { get; private set; }
        public string Exit { get; private set; }

        public WorkSession(string entrance, string exit)
        {
            Entrance = entrance;
            Exit = exit;
        }

        public override bool Equals(object obj)
        {
            return Equals(obj as WorkSession);
        }
        public bool Equals(WorkSession other)
        {
            return other != null &&
                   Entrance == other.Entrance &&
                   Exit == other.Exit;
        }
        public override int GetHashCode()
        {
            var hashCode = 1257807568;
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Entrance);
            hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Exit);
            return hashCode;
        }

        public static bool operator ==(WorkSession session1, WorkSession session2)
        {
            return EqualityComparer<WorkSession>.Default.Equals(session1, session2);
        }
        public static bool operator !=(WorkSession session1, WorkSession session2)
        {
            return !(session1 == session2);
        }
    }
}