Freigeben über


System.Object.GetHashCode-Methode

Dieser Artikel enthält ergänzende Hinweise zur Referenzdokumentation für diese API.

Die GetHashCode Methode stellt einen Hashcode für Algorithmen bereit, die schnelle Überprüfungen der Objektgleichheit benötigen. Ein Hashcode ist ein numerischer Wert, der zum Einfügen und Identifizieren eines Objekts in einer hashbasierten Auflistung verwendet wird, z. B. die Dictionary<TKey,TValue> Klasse, die Hashtable Klasse oder einen von der DictionaryBase Klasse abgeleiteten Typ.

Hinweis

Informationen dazu, wie Hashcodes in Hashtabellen und für einige zusätzliche Hashcodealgorithmen verwendet werden, finden Sie im Eintrag "Hash function " in Wikipedia.

Zwei Objekte, die gleich sind, erzeugen gleiche Hashcodes. Die Umgekehrte ist jedoch nicht wahr: Gleiche Hashcodes implizieren keine Objektgleichheit, da verschiedene (ungleiche) Objekte identische Hashcodes aufweisen können. Darüber hinaus garantiert .NET nicht die Standardimplementierung der GetHashCode Methode, und der Wert, den diese Methode zurückgibt, kann sich zwischen .NET-Implementierungen, wie z. B. verschiedenen Versionen von .NET Framework und .NET Core, und Plattformen wie 32-Bit- und 64-Bit-Plattformen unterscheiden. Verwenden Sie aus diesen Gründen die Standardimplementierung dieser Methode nicht als eindeutigen Objektbezeichner für Hashing-Zwecke. Daraus folgen zwei Folgen:

  • Sie sollten nicht davon ausgehen, dass gleiche Hashcodes die Objektgleichheit implizieren.
  • Sie sollten niemals einen Hashcode außerhalb der Anwendungsdomäne beibehalten oder verwenden, in der sie erstellt wurde, da dasselbe Objekt einen Hash über Anwendungsdomänen, Prozesse und Plattformen hinweg ausführen kann.

Warnung

Ein Hashcode dient zum effizienten Einfügen und Nachschlagen in Auflistungen, die auf einer Hashtabelle basieren. Ein Hashcode ist kein dauerhafter Wert. Aus diesem Grund:

  • Serialisieren Sie keine Hashcodewerte, oder speichern Sie sie in Datenbanken.
  • Verwenden Sie den Hashcode nicht als Schlüssel, um ein Objekt aus einer keyed-Auflistung abzurufen.
  • Senden Sie Hashcodes nicht über Anwendungsdomänen oder Prozesse hinweg. In einigen Fällen können Hashcodes pro Prozess oder Anwendungsdomäne berechnet werden.
  • Verwenden Sie nicht den Hashcode anstelle eines Werts, der von einer kryptografischen Hashfunktion zurückgegeben wird, wenn Sie einen kryptografisch starken Hash benötigen. Verwenden Sie für kryptografische Hashes eine von der System.Security.Cryptography.HashAlgorithm Oder-Klasse System.Security.Cryptography.KeyedHashAlgorithm abgeleitete Klasse.
  • Vermeiden Sie es, die Gleichheit von Hashcodes zu prüfen, um festzustellen, ob zwei Objekte gleich sind. (Ungleiche Objekte können identische Hashcodes aufweisen.) Um die Gleichheit zu testen, rufen Sie die ReferenceEquals- oder Equals-Methode auf.

Die GetHashCode-Methode kann von einem abgeleiteten Typ überschrieben werden. Wenn GetHashCode nicht außer Kraft gesetzt wird, werden Hashcodes für Referenztypen durch Aufrufen der Object.GetHashCode Methode der Basisklasse berechnet, die einen Hashcode basierend auf dem Verweis eines Objekts berechnet. Weitere Informationen finden Sie unter RuntimeHelpers.GetHashCode. Mit anderen Worten, zwei Objekte, für die die Methode ReferenceEquals den Wert true zurückgibt, haben identische Hashcodes. Wenn Wertetypen GetHashCode nicht überschreiben, verwendet die ValueType.GetHashCode-Methode der Basisklasse Reflection, um den Hash-Code auf der Grundlage der Werte der Felder des Typs zu berechnen. Mit anderen Worten: Werttypen, deren Felder gleich Werte haben, weisen gleiche Hashcodes auf. Weitere Informationen zum Überschreiben von GetHashCode, finden Sie im Abschnitt "Hinweise für Erben".

Warnung

Wenn Sie die GetHashCode Methode überschreiben, sollten Sie auch die Equals Methode überschreiben und umgekehrt. Wenn Ihre überschreibende Equals-Methode true zurückgibt, wenn zwei Objekte auf Gleichheit getestet werden, muss Ihre überschreibende GetHashCode-Methode denselben Wert für die beiden Objekte zurückgeben.

Wenn ein Objekt, das als Schlüssel in einer Hashtabelle verwendet wird, keine nützliche Implementierung GetHashCodebereitstellt, können Sie einen Hashcodeanbieter angeben, indem Sie eine IEqualityComparer Implementierung für eine der Überladungen des Hashtable Klassenkonstruktors bereitstellen.

Hinweise für die Windows-Runtime

Wenn Sie die GetHashCode Methode für eine Klasse in der Windows-Runtime aufrufen, stellt sie das Standardverhalten für Klassen bereit, die nicht außer Kraft setzen GetHashCode. Dies ist Teil der Unterstützung, die .NET für die Windows-Runtime bereitstellt (siehe .NET-Support für Windows Store-Apps und Windows-Runtime). Klassen in der Windows-Runtime erben Object nicht und implementieren derzeit keinen GetHashCode. Jedoch scheinen sie über ToString, Equals(Object), und GetHashCode Methoden zu verfügen, wenn Sie sie in Ihrem C#- oder Visual Basic-Code verwenden, und das .NET Framework liefert das Standardverhalten für diese Methoden.

Hinweis

Windows-Runtime-Klassen, die in C# oder Visual Basic geschrieben wurden, können die GetHashCode Methode überschreiben.

Beispiele

Eine der einfachsten Methoden zum Berechnen eines Hashcodes für einen numerischen Wert mit demselben oder einem kleineren Bereich als der Int32 Typ besteht darin, einfach diesen Wert zurückzugeben. Das folgende Beispiel zeigt eine solche Implementierung für eine Number Struktur.

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

Häufig verfügt ein Typ über mehrere Datenfelder, die an der Generierung des Hashcodes teilnehmen können. Eine Möglichkeit zum Generieren eines Hashcodes besteht darin, diese Felder mithilfe eines XOR (eXclusive OR) Vorgangs zu kombinieren, wie im folgenden Beispiel gezeigt.

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

Im vorherigen Beispiel wird derselbe Hashcode für (n1, n2) und (n2, n1) zurückgegeben, sodass möglicherweise mehr Kollisionen generiert werden, als wünschenswert sind. Eine Reihe von Lösungen ist verfügbar, sodass Hashcodes in diesen Fällen nicht identisch sind. Eine besteht darin, den Hashcode eines Tuple Objekts zurückzugeben, das die Reihenfolge jedes Felds widerspiegelt. Das folgende Beispiel zeigt eine mögliche Implementierung, die die Tuple<T1,T2> Klasse verwendet. Beachten Sie jedoch, dass sich der Leistungsaufwand beim Instanziieren eines Tuple Objekts erheblich auf die Gesamtleistung einer Anwendung auswirken kann, die große Anzahl von Objekten in Hashtabellen speichert.

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

Eine zweite alternative Lösung umfasst die Gewichtung der einzelnen Hashcodes durch das Linksverschieben der Hashcodes aufeinanderfolgender Felder um zwei oder mehr Bits. Optimalerweise sollten Bits, die über Bit 31 hinaus verschoben werden, nicht verworfen werden, sondern umlaufen. Da die Bits sowohl in C# als auch in Visual Basic von den Left-Shift-Operatoren verworfen werden, muss dazu eine Left-Shift-and-Wrap-Methode wie die folgende erstellt werden:

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

Das folgende Beispiel verwendet diese Shift-and-Wrap-Methode, um den Code der Point-Struktur zu berechnen, die in den vorherigen Beispielen verwendet wurde.

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