Sdílet prostřednictvím


Metoda System.Object.GetHashCode

Tento článek obsahuje doplňující poznámky k referenční dokumentaci pro toto rozhraní API.

Metoda GetHashCode poskytuje kód hash pro algoritmy, které potřebují rychlé kontroly rovnosti objektů. Kód hash je číselná hodnota, která slouží k vložení a identifikaci objektu v kolekci založené na hodnotě hash, jako Dictionary<TKey,TValue> je třída, Hashtable třída nebo typ odvozený z DictionaryBase třídy.

Poznámka:

Informace o tom, jak se kódy hash používají v tabulkách hash a u některých dalších algoritmů hash kódu, najdete v položce Funkce hash na Wikipedii.

Dva objekty, které jsou stejné návratové hash kódy, které jsou stejné. Opačná hodnota však není pravdivá: stejné hashovací kódy neznamenají rovnost objektů, protože různé (nerovné) objekty mohou mít stejné hashovací kódy. Rozhraní .NET navíc nezaručuje výchozí implementaci GetHashCode metody a hodnota, kterou tato metoda vrátí, se může lišit mezi implementacemi .NET, jako jsou různé verze rozhraní .NET Framework a .NET Core a platformy, jako jsou 32bitové a 64bitové platformy. Z těchto důvodů nepoužívejte výchozí implementaci této metody jako jedinečný identifikátor objektu pro účely hash. Následují dva důsledky:

  • Neměli byste předpokládat, že stejné hashovací kódy znamenají rovnost objektů.
  • Nikdy byste neměli uchovávat ani používat kód hash mimo doménu aplikace, ve které byl vytvořen, protože stejný objekt může zatřiďovat hodnoty hash napříč doménami aplikace, procesy a platformami.

Upozorňující

Kód hash je určený pro efektivní vkládání a vyhledávání v kolekcích založených na tabulce hash. Kód hash není trvalou hodnotou. Z tohoto důvodu:

  • Neukládejte hodnoty kódu hash ani je neukládejte do databází.
  • Nepoužívejte kód hash jako klíč k načtení objektu z kolekce klíčů.
  • Neodesílejte kódy hash napříč doménami nebo procesy aplikace. V některýchpřípadechch kódech se v některých případech dá vypočítat pro jednotlivé procesy nebo domény aplikace.
  • Nepoužívejte kód hash místo hodnoty vrácené kryptografickou hashovací funkcí, pokud potřebujete kryptograficky silnou hodnotu hash. Pro kryptografické hodnoty hash použijte třídu odvozenou z System.Security.Cryptography.HashAlgorithm třídy nebo System.Security.Cryptography.KeyedHashAlgorithm třídy.
  • Neotestujte rovnost hodnot hash kódů, abyste zjistili, zda jsou dva objekty stejné. (Nerovné objekty můžou mít identické kódy hash.) Chcete-li otestovat rovnost, zavolejte metodu nebo Equals metoduReferenceEquals.

Metodu GetHashCode lze přepsat odvozeným typem. Pokud GetHashCode není přepsán, kódy hash pro odkazové typy se počítají voláním Object.GetHashCode metody základní třídy, která vypočítá hashovací kód založený na odkazu objektu; další informace najdete v tématu RuntimeHelpers.GetHashCode. Jinými slovy, dva objekty, pro které ReferenceEquals metoda vrací true , mají stejné hashové kódy. Pokud typy hodnot nepřepíší GetHashCode, ValueType.GetHashCode metoda základní třídy používá reflexi k výpočtu kódu hash na základě hodnot polí typu. Jinými slovy, typy hodnot, jejichž pole mají stejné hodnoty, mají stejné hashovací kódy. Další informace o přepsání GetHashCodenaleznete v části Poznámky k dědění.

Upozorňující

Pokud přepíšete metodu GetHashCode , měli byste také přepsat Equalsa naopak. Pokud vaše přepsaná Equals metoda vrátí true při testování rovnosti dva objekty, přepsaná GetHashCode metoda musí vrátit stejnou hodnotu pro oba objekty.

Pokud objekt, který se používá jako klíč v tabulce hash neposkytuje užitečnou implementaci GetHashCode, můžete určit zprostředkovatele kódu hash zadáním IEqualityComparer implementace do jednoho z přetížení Hashtable konstruktoru třídy.

Poznámky k prostředí Windows Runtime

Když voláte metodu GetHashCode třídy v prostředí Windows Runtime, poskytuje výchozí chování tříd, které nepřepíší GetHashCode. Toto je součástí podpory, kterou .NET poskytuje pro prostředí Windows Runtime (viz podpora aplikací pro Windows Store a prostředí Windows Runtime). Třídy v prostředí Windows Runtime nedědí Objecta v současné době neimplementují GetHashCode. Zdá se však, že mají ToString, Equals(Object)a GetHashCode metody při jejich použití v kódu jazyka C# nebo Visual Basic a rozhraní .NET Framework poskytuje výchozí chování pro tyto metody.

Poznámka:

prostředí Windows Runtime třídy napsané v jazyce C# nebo Visual Basic mohou přepsat metoduGetHashCode.

Příklady

Jedním z nejjednodušších způsobů, jak vypočítat kód hash pro číselnou hodnotu, která má stejný nebo menší rozsah než Int32 typ, je jednoduše vrátit tuto hodnotu. Následující příklad ukazuje takovou implementaci struktury 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

Typ má často více datových polí, která se mohou účastnit generování kódu hash. Jedním ze způsobů, jak vygenerovat hashovací kód, je kombinovat tato pole pomocí XOR (eXclusive OR) operace, jak je znázorněno v následujícím příkladu.

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

Předchozí příklad vrátí stejný hashovací kód pro (n1, n2) a (n2, n1), takže může generovat více kolizí, než je žádoucí. K dispozici je řada řešení, aby v těchto případech nebyly identické kódy hash. Jedním z nich je vrácení hash kódu objektu Tuple , který odráží pořadí každého pole. Následující příklad ukazuje možnou Tuple<T1,T2> implementaci, která používá třídu. Všimněte si ale, že režie na výkon vytváření Tuple instancí objektu může výrazně ovlivnit celkový výkon aplikace, která ukládá velký počet objektů v tabulkách hash.

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

Druhé alternativní řešení zahrnuje vážení jednotlivých hashových kódů tak, že posunete kódy hash po sobě jdoucích polí o dva nebo více bitů. V optimálním případě by se bity posunuté za bit 31 měly obtékat místo zahození. Vzhledem k tomu, že operátory levého posunu v jazyce C# i Visual Basic zahodí bity, vyžaduje vytvoření metody posunu vlevo a zalamování, jako je následující:

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

Následující příklad pak použije tuto metodu shift-and-wrap k výpočtu kódu Point hash struktury použité v předchozích příkladech.

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