System.Object.GetHashCode, metoda

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Metoda GetHashCode udostępnia kod skrótu dla algorytmów wymagających szybkiego sprawdzania równości obiektów. Kod skrótu to wartość liczbowa używana do wstawiania i identyfikowania obiektu w kolekcji opartej na skrótach, takich jak Dictionary<TKey,TValue> klasa, Hashtable klasa lub typ pochodzący z DictionaryBase klasy.

Uwaga

Aby uzyskać informacje na temat sposobu użycia kodów skrótów w tabelach skrótów i dodatkowych algorytmów kodu skrótu, zobacz wpis Funkcji skrótu w Wikipedii.

Dwa obiekty, które są równe kody skrótów zwracanych, które są równe. Jednak odwrotnie nie ma wartości true: równe kody skrótów nie oznaczają równości obiektów, ponieważ różne (nierówne) obiekty mogą mieć identyczne kody skrótów. Ponadto platforma .NET nie gwarantuje domyślnej GetHashCode implementacji metody, a wartość zwracana przez tę metodę może się różnić między implementacjami platformy .NET, takimi jak różne wersje platform .NET Framework i .NET Core, takie jak platformy 32-bitowe i 64-bitowe. Z tych powodów nie należy używać domyślnej implementacji tej metody jako unikatowego identyfikatora obiektu do celów tworzenia skrótów. Z tego wynikają dwie konsekwencje:

  • Nie należy zakładać, że równe kody skrótów oznaczają równość obiektów.
  • Nigdy nie należy utrwalać ani używać kodu skrótu poza domeną aplikacji, w której został utworzony, ponieważ ten sam obiekt może zawierać skrót między domenami aplikacji, procesami i platformami.

Ostrzeżenie

Kod skrótu jest przeznaczony do wydajnego wstawiania i wyszukiwania w kolekcjach opartych na tabeli skrótów. Kod skrótu nie jest wartością stałą. Z tego powodu:

  • Nie serializuj wartości kodu skrótu ani nie przechowuj ich w bazach danych.
  • Nie należy używać kodu skrótu jako klucza do pobierania obiektu z kolekcji kluczy.
  • Nie wysyłaj kodów skrótów między domenami aplikacji ani procesami. W niektórych przypadkach kody skrótów mogą być obliczane dla poszczególnych procesów lub dla domeny aplikacji.
  • Nie używaj kodu skrótu zamiast wartości zwracanej przez funkcję skrótów kryptograficznych, jeśli potrzebujesz skrótu kryptograficznego silnego skrótu. W przypadku skrótów kryptograficznych użyj klasy pochodnej System.Security.Cryptography.HashAlgorithm z klasy lub System.Security.Cryptography.KeyedHashAlgorithm .
  • Nie testuj równości kodów skrótów, aby określić, czy dwa obiekty są równe. (Nierówne obiekty mogą mieć identyczne kody skrótów). Aby przetestować równość, wywołaj metodę ReferenceEquals or Equals .

Metodę GetHashCode można zastąpić typem pochodnym. Jeśli GetHashCode nie jest zastępowany, kody skrótów dla typów odwołań są obliczane przez wywołanie Object.GetHashCode metody klasy bazowej, która oblicza kod skrótu na podstawie odwołania do obiektu; aby uzyskać więcej informacji, zobacz RuntimeHelpers.GetHashCode. Innymi słowy, dwa obiekty, dla których ReferenceEquals metoda zwraca true , mają identyczne kody skrótów. Jeśli typy wartości nie zastępują GetHashCode, ValueType.GetHashCode metoda klasy bazowej używa odbicia w celu obliczenia kodu skrótu na podstawie wartości pól typu. Innymi słowy, typy wartości, których pola mają równe wartości, mają równe kody skrótów. Aby uzyskać więcej informacji na temat zastępowania GetHashCode, zobacz sekcję "Uwagi do dziedziczy".

Ostrzeżenie

Jeśli zastąpisz metodę GetHashCode , należy również zastąpić Equalsmetodę , i na odwrót. Jeśli metoda przesłonięć Equals zwraca true wartość, gdy dwa obiekty są testowane pod kątem równości, metoda zastępowana GetHashCode musi zwrócić tę samą wartość dla dwóch obiektów.

Jeśli obiekt, który jest używany jako klucz w tabeli skrótów, nie zapewnia użytecznej GetHashCodeimplementacji , można określić dostawcę kodu skrótu, podając implementację IEqualityComparer do jednego z przeciążeń Hashtable konstruktora klasy.

Uwagi dotyczące środowisko wykonawcze systemu Windows

Wywołanie GetHashCode metody w klasie w środowisko wykonawcze systemu Windows zapewnia domyślne zachowanie klas, które nie zastępują GetHashCodeklasy . Jest to część obsługi zapewnianej przez platformę .NET dla środowisko wykonawcze systemu Windows (zobacz Obsługa platformy .NET dla aplikacji ze Sklepu Windows i środowisko wykonawcze systemu Windows). Klasy w środowisko wykonawcze systemu Windows nie dziedziczą Object, a obecnie nie implementują klasy GetHashCode. Wydają się one jednak mieć ToStringmetody , Equals(Object)i GetHashCode podczas ich używania w kodzie języka C# lub Visual Basic, a program .NET Framework zapewnia domyślne zachowanie tych metod.

Uwaga

środowisko wykonawcze systemu Windows klasy napisane w języku C# lub Visual Basic mogą zastąpić metodę GetHashCode .

Przykłady

Jednym z najprostszych sposobów obliczania kodu skrótu dla wartości liczbowej, która ma taki sam lub mniejszy zakres niż Int32 typ, jest po prostu zwrócenie tej wartości. W poniższym przykładzie pokazano taką implementację Number struktury.

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

Często typ zawiera wiele pól danych, które mogą uczestniczyć w generowaniu kodu skrótu. Jednym ze sposobów wygenerowania kodu skrótu jest połączenie tych pól przy użyciu XOR (eXclusive OR) operacji, jak pokazano w poniższym przykładzie.

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

Poprzedni przykład zwraca ten sam kod skrótu dla (n1, n2) i (n2, n1), a więc może wygenerować więcej kolizji niż są pożądane. Dostępnych jest wiele rozwiązań, dzięki czemu kody skrótów w tych przypadkach nie są identyczne. Jednym z nich jest zwrócenie kodu skrótu Tuple obiektu, który odzwierciedla kolejność każdego pola. W poniższym przykładzie przedstawiono możliwą implementację używającą Tuple<T1,T2> klasy . Należy jednak pamiętać, że obciążenie związane z wydajnością tworzenia wystąpienia Tuple obiektu może znacząco wpłynąć na ogólną wydajność aplikacji, która przechowuje dużą liczbę obiektów w tabelach skrótów.

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

Drugie alternatywne rozwiązanie polega na ważoniu poszczególnych kodów skrótów przez przesunięcie lewego skrótu kodów kolejnych pól o dwa lub więcej bitów. Optymalnie bity przesunięte poza bit 31 powinny owinąć, a nie zostać odrzucone. Ponieważ bity są odrzucane przez operatory przesunięcia po lewej stronie w językach C# i Visual Basic, wymaga to utworzenia metody shift-and-wrap w lewo, takiej jak następujące:

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

W poniższym przykładzie użyto tej metody shift-and-wrap do obliczenia kodu skrótu Point struktury użytej w poprzednich przykładach.

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