为什么C#字典不会调用隐藏的GetHashCode方法

时间:2016-03-21 16:12:05

标签: c# dictionary

我有一个项目,我正在广泛使用通用C#dictionary。我需要复合键,所以我一直使用tuples作为键。在某些时候,我想知道使用缓存哈希码的自定义类是否有益:

public class CompositeKey<T1, T2> : Tuple<T1, T2>
{
    private readonly int _hashCode;

    public CompositeKey(T1 t1, T2 t2) : base(t1, t2)
    {
        _hashCode = base.GetHashCode();
    }

    public new int GetHashCode()
    {
        return _hashCode;
    }
}

我使用new代替override,因为我认为这对于此测试没有任何影响,因为我使用具体类型定义字典:

var dict = new Dictionary<CompositeKey<string, int>, int>();
但是,我注意到我的自定义GetHashCode方法根本没有被调用。当我将new更改为override时,它会按预期调用。

有人可以解释为什么不调用隐藏的GetHashCode方法吗?如果我像这样定义字典

,我会期待这种行为
var dict = new Dictionary<Tuple<string, int>, int>();

但如果我在示例中明确指定CompositeKey类型,则不行。

P.S。我知道隐藏GetHashCode方法可能不是一个好主意。

4 个答案:

答案 0 :(得分:10)

  

有人可以解释为什么不调用隐藏的GetHashCode方法吗?   如果我要定义字典,我会期待这种行为   此

为了能够调用CompositeKey.GetHashCode方法,必须在编译时将CompositeKey实例的引用输入为CompositeKey

Dictionary<TKey,TValue>的代码库并不知道您的CompositeKey类(显然)。它所知道的只是TKey(泛型类型参数),它与没有任何约束的System.Object等价。因为您无法在没有约束的情况下调用T中声明的System.Object以外的任何方法。

因此,字典最终会调用Object.GetHashCode而不会在您的类中重写 - 因此它不会被调用。

答案 1 :(得分:3)

泛型类型中方法调用的重载解析在未编译的泛型类型(例如Dictionary<TKey, TValue>)编译时发生,而不是在构造闭合类型(例如Dictionary<CompositeKey<string, int>, int>)时发生。

由于TKey中的Dictionary<,>没有约束,GetHashCode()唯一可用的重载是object.GetHashCode()。构造具有更好的GetHashCode()重载的类型不会改变初始重载决策。

它不仅限于隐藏new的方法。重载方法也是如此:

class Generic<T>
{
    public bool Equal(T t1, T t2)
    {
        return t1.Equals(t2);
    }
}

class X : IEquatable<X>
{
    public override bool Equals(object obj)
    {
        Console.WriteLine("object.Equals");
        return true;
    }

    public bool Equals(X other)
    {
        Console.WriteLine("IEquatable.Equals");
        return true;
    }
}

永远不会使用X.Equals(X)重载

var test = new Generic<X>();
test.Equal(new X(), new X()); 

// prints "object.Equals"

答案 2 :(得分:1)

只是详细说明以前的答案。使用new的问题是它只会覆盖方法,如果消费者直接在类上操作(在本例中是你的CompositeKey类)。对你的CompositeKey派生的任何基类的任何调用都不会调用你的新成员。

所以,如果在下面:

  • CompositeKey.GetHashCode()&lt; ---将调用您的新方法。
  • Tuple.GetHashCode()&lt; --- 会调用您的新方法。
  • Object.GetHashCode()&lt; --- 会调用您的新方法。

正如先前的答案所强调的那样,因为EqualityComparer(类字典使用)指定T是非约束泛型,那么编译器将仅支持所有T 的最小公分母传递给它,这是直接在Object上的方法。

因此调用是有效的:((Object)key)。GetHashCode()。从上面你可以看到这不会调用你的新方法。

答案 3 :(得分:0)

这是因为泛型的类型限制。这是一个简化的程序来显示问题。

public class Program
{
    public static void Main(string[] args)
    {
        var bar = new Bar();
        TestMethod(bar);
        TestMethod2(bar);
    }

    public static void TestMethod<T>(T obj) where T : Foo
    {
        obj.Test();
        obj.Test2();
    }

    public static void TestMethod2<T>(T obj) where T : Bar
    {
        obj.Test();
        obj.Test2();
    }
}

public class Foo
{
    public virtual void Test()
    {
        Debugger.Break();
    }

    public virtual void Test2()
    {
        Debugger.Break();
    }
}

public class Bar : Foo
{
    public new void Test()
    {
        Debugger.Break();
    }

    public override void Test2()
    {
        Debugger.Break();
    }
}

TestMethod()中,您点击了Foo.Test()Bar.Test2()中的断点,但在TestMethod2()中,您点击了Bar.Test()Bar.Test2()中的断点,是因为在第一种方法中你被约束为Foo或更低的类型,所以当编译器编译时它绑定到Foo上的调用,就像函数写成

一样
public static void TestMethod<T>(T obj)
{
    ((Foo)obj).Test(); //You would expect this to call Foo.Test() b\c of shadowing
    ((Foo)obj).Test2(); //You would expect this to call Bar.Test2() b\c of overloading
}

现在,关于您的问题,正在使用的比较器是written as

[Serializable]
internal class ObjectEqualityComparer<T>: EqualityComparer<T>
{
    [Pure]
    public override bool Equals(T x, T y) {
        if (x != null) {
            if (y != null) return x.Equals(y);
            return false;
        }
        if (y != null) return false;
        return true;
    }

    [Pure]
    public override int GetHashCode(T obj) {
        if (obj == null) return 0;
        return obj.GetHashCode();
    }
    //...
}

T没有约束,因此这两种方法的行为就好像它们被写成

一样
    public override bool Equals(T x, T y) {
        if (x != null) {
            if (y != null) return ((object)x).Equals(y);
            return false;
        }
        if (y != null) return false;
        return true;
    }

    [Pure]
    public override int GetHashCode(T obj) {
        if (obj == null) return 0;
        return ((object)obj).GetHashCode();
    }

这就是为什么你的功能只在你覆盖它时调用,而不是在你遮蔽它时调用。