不调用以元组为键的字典 GetHashCode

Dictionary with Tuple as Key GetHashCode is not called

描述


正在尝试创建一个以元组为键的字典。

但是 GetHashCode 和 Equals 函数没有被调用,因此重复的键将被添加到字典中。

这是我想用作字典键的 Keyclass:

class Key : IEqualityComparer<Tuple<int, int>>
    {
        private Tuple<int, int> _tuple;

        public Key(int a, int b)
        {
            _tuple = new Tuple<int, int>(a, b);
        }


        public bool Equals(Tuple<int, int> x, Tuple<int, int> y)
        {
            return (x.Item1 == y.Item1 && x.Item2 == y.Item2);
        }

        public int GetHashCode(Tuple<int, int> obj)
        {
            return obj.Item1.GetHashCode() ^ obj.Item2.GetHashCode();
        }
    }

Driver代码:

public static void Main() {

    var map = new Dictionary<Key, int>();

    map.Add(new Key(1, 2), 3);
    map.Add(new Key(1, 2), 4); // <==== Should not add!
}

问题


如何解决这个问题?

Dictionary<Tuple<int, int>, int> 正常工作的最简单实施是什么?

您可以尝试以下解决方案。

public class Key : IEquatable<Key>
{
    private Tuple<int, int> _tuple;

    public Key(int a, int b)
    {
        _tuple = new Tuple<int, int>(a, b);
    }

    public bool Equals(Key other)
    {
        return (this.GetHashCode() == other.GetHashCode());
    }

    public override int GetHashCode()
    {
        return _tuple.GetHashCode();
    }
}

另一种方法是使用 ValueTuple 作为键,默认情况下会根据它的值进行比较。

public static void Main() 
{
    var map = new Dictionary<(int, int), int>();

    map.Add((1, 2), 3);
    map.Add((1, 2), 4); // Throw an exception
}

如果您想拥有自己的 class 来表示一个键,您可以简单地创建 Tuple<int, int> 的子 class 并获得所需的行为 "for free"

public class Key : Tuple<int, int>
{
    public Key(int item1, int item2) : base(item1, item2)
    {
    }
}

如果你想使用自己的 class Key:

    public class Key
    {
        public Key(int item1, int item2)
        {
            Tuple = new Tuple<int, int>(item1, item2);
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
            {
                return false;
            }

            if (obj is Key other)
            {
                return Tuple.Equals(other.Tuple);
            }

            return false;
        }

        public override int GetHashCode()
        {
            return Tuple.GetHashCode();
        }

        public Tuple<int, int> Tuple { get; private set; }
    }

    public void Do()
    {
        var map = new Dictionary<Key, int>();
        map.Add(new Key(1, 2), 3);
        map.Add(new Key(1, 2), 4); // will throw System.ArgumentException
    }

另一种方法就是使用 Tuple class:

    public void Do()
    {
        var map = new Dictionary<Tuple<int, int>, int>();
        map.Add(new Tuple<int, int>(1, 2), 3);
        map.Add(new Tuple<int, int>(1, 2), 4); // will throw System.ArgumentException
    }

问题是,在向字典添加项目时,会调用默认的 EqualsGetHashCode 方法,它们使用引用比较来确定是否相等。

如果要覆盖此行为,则需要使用 override 关键字,并覆盖方法:

class Key : IEquatable<Key>
{
    private readonly Tuple<int, int> tuple;

    public Key(int a, int b)
    {
        tuple = new Tuple<int, int>(a, b);
    }

    public bool Equals(Key other)
    {
        return other != null && 
            tuple.Item1 == other.tuple.Item1 && 
            tuple.Item2 == other.tuple.Item2;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Key);
    }

    public override int GetHashCode()
    {
        return tuple.Item1.GetHashCode() ^ tuple.Item2.GetHashCode();
    }
}

只是在此处发布@Dmitri 答案的简化版本作为参考。

最简单的方法(无需安装额外的包)并且不实现任何接口,就是重写 EqualsGetHashCode 方法,如下所示:

public class Key
{
    private readonly Tuple<int, int> _tuple;

    public Key(int item1, int item2)
    {
        _tuple = new Tuple<int, int>(item1, item2);
    }

    public override bool Equals(object obj)
    {
        var other = obj as Key;
        return _tuple.Item1 == other?._tuple.Item1 && _tuple.Item2 == other?._tuple.Item2;
    }

    public override int GetHashCode()
    {
        return _tuple.Item1.GetHashCode() ^ _tuple.Item2.GetHashCode();
    }

}