2008-09-19 25 views
12

這個問題來自tuples的討論。c#如何找出對象的哈希碼?

我開始考慮元組應該擁有的哈希碼。 如果我們將KeyValuePair類作爲一個元組接受,該怎麼辦?它不重寫GetHashCode()方法,所以它可能不會意識到它的「孩子」的哈希碼......所以,運行時將調用Object.GetHashCode(),它不知道真實的物體結構。

然後,我們可以製作兩個實例,因爲重載的GetHashCode()和Equals(),它們實際上是Equal。並用它們作爲元組中的「孩子」來「欺騙」字典。

但它不起作用!運行時以某種方式計算出我們元組的結構並調用我們類的重載GetHashCode!

它是如何工作的? Object.GetHashCode()做了什麼分析?

當我們使用一些複雜的按鍵時,會不會影響性能? (可能是不可能的場景......但還是)

考慮以下代碼爲例:

namespace csharp_tricks 
{ 
    class Program 
    { 
     class MyClass 
     { 
      int keyValue; 
      int someInfo; 

      public MyClass(int key, int info) 
      { 
       keyValue = key; 
       someInfo = info; 
      } 

      public override bool Equals(object obj) 
      { 
       MyClass other = obj as MyClass; 
       if (other == null) return false; 

       return keyValue.Equals(other.keyValue); 
      } 

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

     static void Main(string[] args) 
     { 
      Dictionary<object, object> dict = new Dictionary<object, object>(); 

      dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 1), 1), 1); 

      //here we get the exception -- an item with the same key was already added 
      //but how did it figure out the hash code? 
      dict.Add(new KeyValuePair<MyClass,object>(new MyClass(1, 2), 1), 1); 

      return; 
     } 
    } 
} 

更新我想我找到了在我的答案在下面說明該解釋。它的主要成果是:

  • 小心你的鑰匙和他們的哈希碼:-)
  • 對於複雜的字典鍵,你必須正確地重寫的equals()和GetHashCode()。

回答

1

看來我現在有一個線索。

我以爲KeyValuePair是一個引用類型,但它不是,它是一個結構體。所以它使用ValueType.GetHashCode()方法。 MSDN爲它說:「派生類型的一個或多個字段用於計算返回值」。

如果你將一個真正的引用類型作爲「元組提供者」,你會欺騙字典(或你自己......)。

using System.Collections.Generic; 

namespace csharp_tricks 
{ 
    class Program 
    { 
     class MyClass 
     { 
      int keyValue; 
      int someInfo; 

      public MyClass(int key, int info) 
      { 
       keyValue = key; 
       someInfo = info; 
      } 

      public override bool Equals(object obj) 
      { 
       MyClass other = obj as MyClass; 
       if (other == null) return false; 

       return keyValue.Equals(other.keyValue); 
      } 

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

     class Pair<T, R> 
     { 
      public T First { get; set; } 
      public R Second { get; set; } 
     } 

     static void Main(string[] args) 
     { 
      var dict = new Dictionary<Pair<int, MyClass>, object>(); 

      dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 2) }, 1); 

      //this is a pair of the same values as previous! but... no exception this time... 
      dict.Add(new Pair<int, MyClass>() { First = 1, Second = new MyClass(1, 3) }, 1); 

      return; 
     } 
    } 
} 
0

我沒有書的參考了,我不得不去找,只是爲了確認,但我認爲默認的基準散列只是將所有對象的成員散列在一起。由於CLR的工作方式,它可以訪問它們,所以它不是你可以寫得和他們一樣好的東西。

這完全來自於我簡要閱讀的東西的記憶,所以把它當作你的意願。

編輯:這本書是裏面的C#從MS出版社。帶鋸條的蓋子上。筆者花了很多時間解釋CLR中的實現方式,語言如何轉換爲MSIL等。等。如果你能找到這本書,這不是一個錯誤的閱讀。

編輯:表格的鏈接提供,它看起來像

Object.GetHashCode()使用一個內部 字段在System.Object類來生成散列值。每創建一個對象 時,都會爲其創建一個唯一的對象鍵,並將其作爲整數存儲。這些鍵從1開始,每次創建任何類型的新對象時都會增加。

嗯我想我需要寫一些我自己的散列碼,如果我希望使用對象作爲散列鍵。

+0

這種解釋與代碼示例矛盾的問題。 – 2008-09-19 15:47:28

7

這是GetHashCode的一個很好的文章,從有效的C#:http://www.awprofessional.com/content/images/0321245660/items/wagner_item10.pdf

+0

有趣的文章,但它顯然包含Object.GetHashCode如何工作的錯誤解釋。如果它將如文章中所述,那麼將不會發生異常...... – 2008-09-19 15:35:35

+0

GetHashCode示例實現對於無用點來說是微不足道的。如果不止一個領域參與對象的平等呢?這似乎是我更常見的情況。 – 2009-03-13 15:11:13

+0

@Turbulent:在這種情況下,您應該使用異或操作(^),請參閱 – 2009-05-21 08:53:02

14

不要在可變類重寫GetHashCode()和equals(),如果修改一個對象只覆蓋它一成不變的類或結構,否則用作密鑰散列表將無法正常運行(在密鑰對象被修改後,您將無法檢索與密鑰相關聯的值)

另外,哈希表不使用哈希碼來標識對象使用鍵對象自己作爲標識符,並不要求所有用於在哈希表中添加條目的鍵都返回不同的哈希碼,但建議他們這樣做,否則性能會受到很大影響。

+1

問題的其他答案好,但這不是答案。 – 2008-09-19 15:33:32

+0

但是他們怎麼可能在不產生散列的情況下識別對象呢?這不是GetHashCode的重點嗎? – 2008-09-19 15:50:11

2

請查看Brad Abrams的post以及Brian Grunkemeyer對於object.GetHashCode如何工作的更多信息的評論。另外,請看Ayande的博客post的第一條評論。我不知道該框架的當前版本是否仍然遵循這些規則,或者他們是否已經像Brad所暗示的那樣實際改變了它。

-1

所以它可能不會意識到它的「孩子」的哈希碼。

你的例子似乎證明並非如此:-)爲重點MyClass的哈希碼和值1是兩個KeyValuePair的相同。 KeyValuePair實現必須使用其KeyValue作爲其自己的散列代碼

向上移動,字典類需要唯一鍵。它使用每個密鑰提供的哈希碼來解決問題。請記住,運行時不調用Object.GetHashCode(),但它調用您提供的實例提供的GetHashCode()實現。

考慮更復雜的情況:

public class HappyClass 
{ 


    enum TheUnit 
    { 
     Points, 
     Picas, 
     Inches 
    } 

    class MyDistanceClass 
    { 
     int distance; 
     TheUnit units; 

     public MyDistanceClass(int theDistance, TheUnit unit) 
     { 
      distance = theDistance; 

      units = unit; 
     } 
     public static int ConvertDistance(int oldDistance, TheUnit oldUnit, TheUnit newUnit) 
     { 
      // insert real unit conversion code here :-) 
      return oldDistance * 100; 
     } 

     /// <summary> 
     /// Figure out if we are equal distance, converting into the same units of measurement if we have to 
     /// </summary> 
     /// <param name="obj">the other guy</param> 
     /// <returns>true if we are the same distance</returns> 
     public override bool Equals(object obj) 
     { 
      MyDistanceClass other = obj as MyDistanceClass; 
      if (other == null) return false; 

      if (other.units != this.units) 
      { 
       int newDistance = MyDistanceClass.ConvertDistance(other.distance, other.units, this.units); 
       return distance.Equals(newDistance); 
      } 
      else 
      { 
       return distance.Equals(other.distance); 
      } 


     } 

     public override int GetHashCode() 
     { 
      // even if the distance is equal in spite of the different units, the objects are not 
      return distance.GetHashCode() * units.GetHashCode(); 
     } 
    } 
    static void Main(string[] args) 
    { 

     // these are the same distance... 72 points = 1 inch 
     MyDistanceClass distPoint = new MyDistanceClass(72, TheUnit.Points); 
     MyDistanceClass distInch = new MyDistanceClass(1, TheUnit.Inch); 

     Debug.Assert(distPoint.Equals(distInch), "these should be true!"); 
     Debug.Assert(distPoint.GetHashCode() != distInch.GetHashCode(), "But yet they are fundimentally different values"); 

     Dictionary<object, object> dict = new Dictionary<object, object>(); 

     dict.Add(new KeyValuePair<MyDistanceClass, object>(distPoint, 1), 1); 

     //this should not barf 
     dict.Add(new KeyValuePair<MyDistanceClass, object>(distInch, 1), 1); 

     return; 
    } 

} 

基本上......在我的例子的情況下,你會希望兩個對象是相同的距離,返回「真」爲平等相待,但但返回不同的哈希碼。

3

下面是四元組(包含4個元組內部組件)內部適當的哈希和相等實現。此代碼確保在HashSets和字典中正確使用此特定元組。

更多關於這個問題的信息(包括源代碼)here

用法選中關鍵字的(以避免溢出)和投擲的NullReferenceException如果obj爲空(所要求的基方法)

public override bool Equals(object obj) 
{ 
    if (ReferenceEquals(null, obj)) 
     throw new NullReferenceException("obj is null"); 
    if (ReferenceEquals(this, obj)) return true; 
    if (obj.GetType() != typeof (Quad<T1, T2, T3, T4>)) return false; 
    return Equals((Quad<T1, T2, T3, T4>) obj); 
} 

public bool Equals(Quad<T1, T2, T3, T4> obj) 
{ 
    if (ReferenceEquals(null, obj)) return false; 
    if (ReferenceEquals(this, obj)) return true; 
    return Equals(obj.Item1, Item1) 
     && Equals(obj.Item2, Item2) 
      && Equals(obj.Item3, Item3) 
       && Equals(obj.Item4, Item4); 
} 

public override int GetHashCode() 
{ 
    unchecked 
    { 
     int result = Item1.GetHashCode(); 
     result = (result*397)^Item2.GetHashCode(); 
     result = (result*397)^Item3.GetHashCode(); 
     result = (result*397)^Item4.GetHashCode(); 
     return result; 
    } 
} 
public static bool operator ==(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right) 
{ 
    return Equals(left, right); 
} 


public static bool operator !=(Quad<T1, T2, T3, T4> left, Quad<T1, T2, T3, T4> right) 
{ 
    return !Equals(left, right); 
}