[英]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派生的任何基類的任何調用都不會調用您的新成員。
所以,如果在下面:
正如先前的答案所強調的,因為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.