2016-03-21 61 views
7

我有一個項目,我廣泛使用通用C#dictionary。我需要複合鍵,所以我一直在使用tuples作爲鍵。在某些時候,我不知道這是否將是有益的要使用的緩存哈希代碼自定義類:爲什麼C#字典不調用隱藏的GetHashCode方法

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方法可能不是一個好主意。

+1

隱藏是一個壞主意,因爲它會導致此問題! – AndyJ

+2

這裏有一篇文章解釋新的和重寫的區別... http://stackoverflow.com/questions/1399127/difference-between-new-and-override – AndyJ

+0

作爲一邊,如果你認爲你的hashcode算法需要緩存它可能是一個不好的算法,或者你不必要地緩存。 –

回答

9

有人可以解釋爲什麼隱藏的GetHashCode方法不叫:與重載方法同樣的情況? 我希望這個行爲,如果我將定義字典像 這

爲了能夠調用CompositeKey.GetHashCode方法,一個必須在編譯時類型爲CompositeKeyCompositeKey實例的引用。

Dictionary<TKey,TValue>的代碼庫不知道你的CompositeKey類(顯然)。它所知道的是TKey(泛型類型參數),它與沒有任何約束的System.Object等價。因爲除了沒有限制地在System.Object中聲明的T之外,您不能調用任何方法。

所以,字典最後調用Object.GetHashCode這是你的類沒有覆蓋 - 因此它不被稱爲。

3

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

由於TKeyDictionary<,>沒有限制,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" 
+0

謝謝你的例子。我不知道當你試圖構造一個泛型類型的新實例時,除了類型檢查之外,這些約束還有其他任何影響。 – fknx

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

這就是爲什麼你的函數,只有當你推翻它,而不是當你的陰影叫沒有限制。

1

只是爲了闡述以前的答案。新問題在於,它只覆蓋方法,如果消費者直接操作類(在本例中爲CompositeKey類)。對CompositeKey派生自的任何基類的任何調用都不會調用您的新成員。

所以,如果在以下幾點:

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

由於以前的答案都強調,因爲EqualityComparer(類字典使用)規定,T是一個非約束通用的,那麼編譯器將只支持對所有T可能被傳遞到最小公分母它,這是直接在Object上的方法。

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

相關問題