簡體   English   中英

為什么C#字典不會調用隱藏的GetHashCode方法

[英]Why does the C# dictionary not call the hidden GetHashCode method

我有一個項目,我廣泛使用通用的C# 字典 我需要復合鍵,所以我一直使用元組作為鍵。 在某些時候,我想知道使用緩存哈希碼的自定義類是否有益:

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類型則不行。

PS我知道隱藏GetHashCode方法可能不是一個好主意。

有人可以解釋為什么不調用隱藏的GetHashCode方法嗎? 如果我像這樣定義字典,我會期待這種行為

為了能夠調用CompositeKey.GetHashCode方法,必須有實例的引用CompositeKey類型為CompositeKey在編譯的時候。

但是Dictionary<TKey,TValue>代碼庫不知道你的CompositeKey類(顯然)。 它所知道的只是TKey (泛型類型參數),它與沒有任何約束的System.Object等效。 因為你不能調用任何T方法,而不是在沒有約束的情況下在System.Object聲明。

因此,Dictionary最終會調用Object.GetHashCode ,它不會在您的類中被覆蓋 - 因此它不會被調用。

泛型類型中的方法調用的重載解析發生在編譯未綁定泛型類型(例如Dictionary<TKey, TValue> )時,而不是在構造閉合類型(例如Dictionary<CompositeKey<string, int>, int> )時。

由於Dictionary<,>沒有對TKey約束,因此可用的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"

只是詳細說明以前的答案。 使用new的問題是它只會覆蓋方法,如果消費者直接在類上操作(在本例中是您的CompositeKey類)。對您的CompositeKey派生的任何基類的任何調用都不會調用您的新成員。

所以,如果在下面:

  • CompositeKey.GetHashCode()<---將調用您的新方法。
  • Tuple.GetHashCode()<--- 不會調用你的新方法。
  • Object.GetHashCode()<--- 不會調用你的新方法。

正如先前的答案所強調的,因為EqualityComparer(類字典使用)指定T是非約束泛型,那么編譯器將僅支持可以傳遞給它的所有T最小公分母 ,這是直接在賓語。

因此調用是有效的:((Object)key)。GetHashCode()。 從上面你可以看到這不會調用你的新方法。

這是因為泛型的類型限制。 這是一個簡化的程序來顯示問題。

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
}

現在,關於您的問題,正在使用的比較器被寫為

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

這就是為什么你的功能只有在你覆蓋它時才被調用,而不是在你遮蔽它時。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM