System.Object.GetHashCode 方法

本文提供此 API 參考文件的補充備註。

方法 GetHashCode 會為需要快速檢查物件是否相等的演算法提供哈希碼。 哈希程式代碼是一個數值,用來在哈希型集合中插入和識別物件,例如 Dictionary<TKey,TValue> 類別、 Hashtable 類別或衍生自 類別的類型 DictionaryBase

注意

如需哈希表和一些其他哈希程式代碼演算法如何使用哈希碼的相關信息,請參閱 Wikipedia中的哈希函 式專案。

相等的兩個 對象會傳回相等的哈希碼。 不過,反向不是真的:相等哈希碼並不表示物件相等,因為不同的 (不相等) 物件可以有相同的哈希碼。 此外,.NET 並不保證方法的默認實 GetHashCode 作,而且這個方法傳回的值在 .NET 實作之間可能會有所不同,例如不同版本的 .NET Framework 和 .NET Core,以及 32 位和 64 位平臺等平臺。 基於這些原因,請勿使用此方法的默認實作做為哈希用途的唯一對象標識碼。 下列兩個結果如下:

  • 您不應該假設相等哈希碼表示物件相等。
  • 您不應該在建立應用程式域外部保存或使用哈希程式代碼,因為相同的物件可能會跨應用程式域、進程和平台進行哈希。

警告

哈希碼適用於以哈希表為基礎的集合中有效率的插入和查閱。 哈希碼不是永久值。 基於這個理由:

  • 請勿串行化哈希碼值,或將它們儲存在資料庫中。
  • 請勿使用哈希碼作為索引鍵,從索引鍵集合擷取物件。
  • 請勿跨應用程式域或行程傳送哈希碼。 在某些情況下,哈希碼可能會以個別進程或個別應用程式域為基礎來計算。
  • 如果您需要密碼編譯強式哈希,請勿使用哈希碼,而不是密碼編譯哈希函式所傳回的值。 針對密碼編譯哈希,請使用衍生自 或 System.Security.Cryptography.KeyedHashAlgorithm 類別的System.Security.Cryptography.HashAlgorithm類別。
  • 請勿測試哈希碼是否相等,以判斷兩個物件是否相等。 (不相等的物件可以有相同的哈希碼。若要測試是否相等,請呼叫 ReferenceEqualsEquals 方法。

GetHashCode方法可由衍生型別覆寫。 如果未 GetHashCode 覆寫,參考型別的哈希碼會藉由呼叫 Object.GetHashCode 基類的 方法來計算,其會根據對象的參考計算哈希碼;如需詳細資訊,請參閱 RuntimeHelpers.GetHashCode。 換句話說,方法傳true回的兩個物件ReferenceEquals具有相同的哈希碼。 如果未覆寫 GetHashCode實值型別, ValueType.GetHashCode 基類的 方法會使用反映,根據型別字段的值來計算哈希碼。 換句話說,欄位具有相等值的實值型別具有相等的哈希碼。 如需覆寫 GetHashCode的詳細資訊,請參閱一節。

警告

如果您覆寫 GetHashCode 方法,則也應該覆寫 Equals,反之亦然。 如果覆寫 Equals 的方法在 true 測試兩個對象時傳回是否相等,則覆寫 GetHashCode 的方法必須針對兩個對象傳回相同的值。

如果做為哈希表中索引鍵的物件未提供 有用的 實 GetHashCode作,您可以藉由 IEqualityComparer 提供實作給類別建構函式的其中一個多載 Hashtable ,來指定哈希程式代碼提供者。

Windows 執行階段的注意事項

當您在 Windows 執行階段 的類別上呼叫 GetHashCode 方法時,它會為未覆寫 GetHashCode的類別提供預設行為。 這是 .NET 為 Windows 執行階段 提供的支援的一部分(請參閱 Windows 市集應用程式和 Windows 執行階段 的 .NET 支援)。 Windows 執行階段 中的類別不會繼承 Object,而且目前不會實GetHashCode作 。 不過,當您在 C# 或 Visual Basic 程式代碼中使用、 和 方法時,它們似乎具有 ToStringEquals(Object)、 和 GetHashCode 方法,而 .NET Framework 會提供這些方法的預設行為。

注意

Windows 執行階段 以 C# 或 Visual Basic 撰寫的類別可以覆寫 GetHashCode 方法。

範例

計算數值哈希碼的最簡單方式之一,其範圍與 Int32 類型相同或更小的數值,就是只傳回該值。 下列範例示範 結構的這類實作 Number

using System;

public struct Number
{
   private int n;

   public Number(int value)
   {
      n = value;
   }

   public int Value
   {
      get { return n; }
   }

   public override bool Equals(Object obj)
   {
      if (obj == null || ! (obj is Number))
         return false;
      else
         return n == ((Number) obj).n;
   }

   public override int GetHashCode()
   {
      return n;
   }

   public override string ToString()
   {
      return n.ToString();
   }
}

public class Example1
{
   public static void Main()
   {
      Random rnd = new Random();
      for (int ctr = 0; ctr <= 9; ctr++) {
         int randomN = rnd.Next(Int32.MinValue, Int32.MaxValue);
         Number n = new Number(randomN);
         Console.WriteLine("n = {0,12}, hash code = {1,12}", n, n.GetHashCode());
      }
   }
}
// The example displays output like the following:
//       n =   -634398368, hash code =   -634398368
//       n =   2136747730, hash code =   2136747730
//       n =  -1973417279, hash code =  -1973417279
//       n =   1101478715, hash code =   1101478715
//       n =   2078057429, hash code =   2078057429
//       n =   -334489950, hash code =   -334489950
//       n =    -68958230, hash code =    -68958230
//       n =   -379951485, hash code =   -379951485
//       n =    -31553685, hash code =    -31553685
//       n =   2105429592, hash code =   2105429592
open System

[<Struct; CustomEquality; NoComparison>]
type Number(value: int) =
    member _.Value = value

    override _.Equals(obj) =
        match obj with
        | :? Number as n ->
            n.Value = value
        | _ -> false

    override _.GetHashCode() =
        value

    override _.ToString() =
        string value

let rnd = Random()
for _ = 0 to 9 do
    let randomN = rnd.Next(Int32.MinValue, Int32.MaxValue)
    let n = Number randomN
    printfn $"n = {n,12}, hash code = {n.GetHashCode(),12}"
// The example displays output like the following:
//       n =   -634398368, hash code =   -634398368
//       n =   2136747730, hash code =   2136747730
//       n =  -1973417279, hash code =  -1973417279
//       n =   1101478715, hash code =   1101478715
//       n =   2078057429, hash code =   2078057429
//       n =   -334489950, hash code =   -334489950
//       n =    -68958230, hash code =    -68958230
//       n =   -379951485, hash code =   -379951485
//       n =    -31553685, hash code =    -31553685
//       n =   2105429592, hash code =   2105429592
Public Structure Number
   Private n As Integer

   Public Sub New(value As Integer)
      n = value
   End Sub

   Public ReadOnly Property Value As Integer
      Get
         Return n
      End Get
   End Property
   
   Public Overrides Function Equals(obj As Object) As Boolean
      If obj Is Nothing OrElse Not TypeOf obj Is Number Then
         Return False
      Else
         Return n = CType(obj, Number).n
      End If
   End Function      
   
   Public Overrides Function GetHashCode() As Integer
      Return n
   End Function
   
   Public Overrides Function ToString() As String
      Return n.ToString()
   End Function
End Structure

Module Example1
    Public Sub Main()
        Dim rnd As New Random()
        For ctr As Integer = 0 To 9
            Dim randomN As Integer = rnd.Next(Int32.MinValue, Int32.MaxValue)
            Dim n As New Number(randomN)
            Console.WriteLine("n = {0,12}, hash code = {1,12}", n, n.GetHashCode())
        Next
    End Sub
End Module
' The example displays output like the following:
'       n =   -634398368, hash code =   -634398368
'       n =   2136747730, hash code =   2136747730
'       n =  -1973417279, hash code =  -1973417279
'       n =   1101478715, hash code =   1101478715
'       n =   2078057429, hash code =   2078057429
'       n =   -334489950, hash code =   -334489950
'       n =    -68958230, hash code =    -68958230
'       n =   -379951485, hash code =   -379951485
'       n =    -31553685, hash code =    -31553685
'       n =   2105429592, hash code =   2105429592

通常,類型具有多個數據欄位,可參與產生哈希程式代碼。 產生哈希程式代碼的其中一 XOR (eXclusive OR) 種方法是使用這些作業來結合這些欄位,如下列範例所示。

using System;

// A type that represents a 2-D point.
public struct Point2
{
    private int x;
    private int y;

    public Point2(int x, int y)
    {
       this.x = x;
       this.y = y;
    }

    public override bool Equals(Object obj)
    {
       if (! (obj is Point2)) return false;

       Point2 p = (Point2) obj;
       return x == p.x & y == p.y;
    }

    public override int GetHashCode()
    {
        return x ^ y;
    }
}

public class Example3
{
   public static void Main()
   {
      Point2 pt = new Point2(5, 8);
      Console.WriteLine(pt.GetHashCode());

      pt = new Point2(8, 5);
      Console.WriteLine(pt.GetHashCode());
   }
}
// The example displays the following output:
//       13
//       13
// A type that represents a 2-D point.
[<Struct; CustomEquality; NoComparison>]
type Point(x: int, y: int) =
    member _.X = x
    member _.Y = y

    override _.Equals(obj) =
        match obj with
        | :? Point as p ->
            x = p.X && y = p.Y
        | _ -> 
            false

    override _.GetHashCode() =
        x ^^^ y

let pt = Point(5, 8)
printfn $"{pt.GetHashCode()}"

let pt2 = Point(8, 5)
printfn $"{pt.GetHashCode()}"
// The example displays the following output:
//       13
//       13
' A type that represents a 2-D point.
Public Structure Point3
    Private x As Integer
    Private y As Integer

    Public Sub New(x As Integer, y As Integer)
        Me.x = x
        Me.y = y
    End Sub

    Public Overrides Function Equals(obj As Object) As Boolean
        If Not TypeOf obj Is Point3 Then Return False

        Dim p As Point3 = CType(obj, Point3)
        Return x = p.x And y = p.y
    End Function

    Public Overrides Function GetHashCode() As Integer
        Return x Xor y
    End Function
End Structure

Public Module Example3
    Public Sub Main()
        Dim pt As New Point3(5, 8)
        Console.WriteLine(pt.GetHashCode())

        pt = New Point3(8, 5)
        Console.WriteLine(pt.GetHashCode())
    End Sub
End Module

上一個範例會針對 (n1、 n2) 和 (n2, n1) 傳回相同的哈希碼,因此可能會產生比預期更多的衝突。 有許多解決方案可供使用,因此在這些情況下哈希碼不相同。 其中一個 Tuple 是傳回反映每個字段順序之物件的哈希碼。 下列範例示範使用 類別的可能實作 Tuple<T1,T2> 。 不過請注意,具現化 Tuple 物件的效能額外負荷可能會大幅影響將大量物件儲存在哈希表中的應用程式整體效能。

using System;

public struct Point3
{
    private int x;
    private int y;

    public Point3(int x, int y)
    {
       this.x = x;
       this.y = y;
    }

    public override bool Equals(Object obj)
    {
        if (obj is Point3)
        {
            Point3 p = (Point3) obj;
            return x == p.x & y == p.y;
        }
        else
        {
            return false;
        }      
    }

    public override int GetHashCode()
    {
        return Tuple.Create(x, y).GetHashCode();
    }
}

public class Example
{
   public static void Main()
   {
        Point3 pt = new Point3(5, 8);
        Console.WriteLine(pt.GetHashCode());

        pt = new Point3(8, 5);
        Console.WriteLine(pt.GetHashCode());
   }
}
// The example displays the following output:
//       173
//       269
[<Struct; CustomEquality; NoComparison>]
type Point(x: int, y: int) =
    member _.X = x
    member _.Y = y

    override _.Equals(obj) =
        match obj with
        | :? Point as p ->
            x = p.X && y = p.Y
        | _ -> 
            false

    override _.GetHashCode() =
        (x, y).GetHashCode()

let pt = Point(5, 8)
printfn $"{pt.GetHashCode()}"

let pt2 = Point(8, 5)
printfn $"{pt2.GetHashCode()}"
// The example displays the following output:
//       173
//       269
Public Structure Point
    Private x As Integer
    Private y As Integer

    Public Sub New(x As Integer, y As Integer)
       Me.x = x
       Me.y = y
    End Sub
    
    Public Overrides Function Equals(obj As Object) As Boolean
       If Not TypeOf obj Is Point Then Return False
       
       Dim p As Point = CType(obj, Point)
       Return x = p.x And y = p.y
    End Function
    
    Public Overrides Function GetHashCode() As Integer 
        Return Tuple.Create(x, y).GetHashCode()
    End Function 
End Structure 

Public Module Example
    Public Sub Main() 
        Dim pt As New Point(5, 8)
        Console.WriteLine(pt.GetHashCode())
        
        pt = New Point(8, 5)
        Console.WriteLine(pt.GetHashCode())
    End Sub 
End Module         
' The example displays the following output:
'       173
'       269

第二個替代解決方案牽涉到將連續字段的哈希碼向左移移兩個或多個位來加權個別哈希碼。 以最佳方式,位移超過位 31 的位應該四處換行,而不是捨棄。 由於 C# 和 Visual Basic 中的左移運算符會捨棄位,因此需要建立左移和換行方法,如下所示:

public int ShiftAndWrap(int value, int positions)
{
    positions = positions & 0x1F;

    // Save the existing bit pattern, but interpret it as an unsigned integer.
    uint number = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
    // Preserve the bits to be discarded.
    uint wrapped = number >> (32 - positions);
    // Shift and wrap the discarded bits.
    return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) | wrapped), 0);
}
let shiftAndWrap (value: int) positions =
    let positions = positions &&& 0x1F

    // Save the existing bit pattern, but interpret it as an unsigned integer.
    let number = BitConverter.ToUInt32(BitConverter.GetBytes value, 0)
    // Preserve the bits to be discarded.
    let wrapped = number >>> (32 - positions)
    // Shift and wrap the discarded bits.
    BitConverter.ToInt32(BitConverter.GetBytes((number <<< positions) ||| wrapped), 0)
Public Function ShiftAndWrap(value As Integer, positions As Integer) As Integer
   positions = positions And &h1F
   
   ' Save the existing bit pattern, but interpret it as an unsigned integer.
   Dim number As UInteger = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0)
   ' Preserve the bits to be discarded.
   Dim wrapped AS UInteger = number >> (32 - positions)
   ' Shift and wrap the discarded bits.
   Return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) Or wrapped), 0)
End Function

下列範例接著會使用此shift-and-wrap方法來計算先前範例中使用的結構的哈希程序代碼 Point

using System;

public struct Point
{
    private int x;
    private int y;

    public Point(int x, int y)
    {
       this.x = x;
       this.y = y;
    }

    public override bool Equals(Object obj)
    {
       if (!(obj is Point)) return false;

       Point p = (Point) obj;
       return x == p.x & y == p.y;
    }

    public override int GetHashCode()
    {
        return ShiftAndWrap(x.GetHashCode(), 2) ^ y.GetHashCode();
    }

    private int ShiftAndWrap(int value, int positions)
    {
        positions = positions & 0x1F;

        // Save the existing bit pattern, but interpret it as an unsigned integer.
        uint number = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
        // Preserve the bits to be discarded.
        uint wrapped = number >> (32 - positions);
        // Shift and wrap the discarded bits.
        return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) | wrapped), 0);
    }
}

public class Example2
{
   public static void Main()
   {
        Point pt = new Point(5, 8);
        Console.WriteLine(pt.GetHashCode());

        pt = new Point(8, 5);
        Console.WriteLine(pt.GetHashCode());
   }
}
// The example displays the following output:
//       28
//       37
open System

[<Struct; CustomEquality; NoComparison>]
type Point(x: int, y: int) =
    member _.X = x
    member _.Y = y
    override _.Equals(obj) =
        match obj with
        | :? Point as p ->
            x = p.X && y = p.Y
        | _ -> 
            false

    override this.GetHashCode() =
        this.ShiftAndWrap(x.GetHashCode(), 2) ^^^ y.GetHashCode()

    member _.ShiftAndWrap(value, positions) =
        let positions = positions &&& 0x1F

        // Save the existing bit pattern, but interpret it as an unsigned integer.
        let number = BitConverter.ToUInt32(BitConverter.GetBytes value, 0)
        // Preserve the bits to be discarded.
        let wrapped = number >>> (32 - positions)
        // Shift and wrap the discarded bits.
        BitConverter.ToInt32(BitConverter.GetBytes((number <<< positions) ||| wrapped), 0)

let pt = Point(5, 8)
printfn $"{pt.GetHashCode()}"

let pt2 = Point(8, 5)
printfn $"{pt2.GetHashCode()}"
// The example displays the following output:
//       28
//       37
Public Structure Point5
    Private x As Integer
    Private y As Integer

    Public Sub New(x As Integer, y As Integer)
        Me.x = x
        Me.y = y
    End Sub

    Public Overrides Function Equals(obj As Object) As Boolean
        If Not TypeOf obj Is Point5 Then Return False

        Dim p As Point5 = CType(obj, Point5)
        Return x = p.x And y = p.y
    End Function

    Public Overrides Function GetHashCode() As Integer
        Return ShiftAndWrap(x.GetHashCode(), 2) Xor y.GetHashCode()
    End Function

    Private Function ShiftAndWrap(value As Integer, positions As Integer) As Integer
        positions = positions And &H1F

        ' Save the existing bit pattern, but interpret it as an unsigned integer.
        Dim number As UInteger = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0)
        ' Preserve the bits to be discarded.
        Dim wrapped As UInteger = number >> (32 - positions)
        ' Shift and wrap the discarded bits.
        Return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) Or wrapped), 0)
    End Function
End Structure

Module Example2
    Public Sub Main()
        Dim pt As New Point5(5, 8)
        Console.WriteLine(pt.GetHashCode())

        pt = New Point5(8, 5)
        Console.WriteLine(pt.GetHashCode())
    End Sub
End Module
' The example displays the following output:
'       28
'       37