Méthode System.Object.GetHashCode

Cet article vous offre des remarques complémentaires à la documentation de référence pour cette API.

La GetHashCode méthode fournit un code de hachage pour les algorithmes qui ont besoin de case activée rapides d’égalité d’objet. Un code de hachage est une valeur numérique utilisée pour insérer et identifier un objet dans une collection basée sur un hachage, telle que la Dictionary<TKey,TValue> classe, la Hashtable classe ou un type dérivé de la DictionaryBase classe.

Remarque

Pour plus d’informations sur l’utilisation des codes de hachage dans les tables de hachage et pour certains algorithmes de code de hachage supplémentaires, consultez l’entrée de fonction de hachage dans Wikipédia.

Deux objets qui sont des codes de hachage de retour égaux qui sont égaux. Toutefois, l’inverse n’est pas vrai : les codes de hachage égaux n’impliquent pas l’égalité des objets, car différents objets (inégaux) peuvent avoir des codes de hachage identiques. En outre, .NET ne garantit pas l’implémentation par défaut de la GetHashCode méthode, et la valeur retournée par cette méthode peut différer entre les implémentations .NET, telles que différentes versions de .NET Framework et .NET Core, et les plateformes, telles que les plateformes 32 bits et 64 bits. Pour ces raisons, n’utilisez pas l’implémentation par défaut de cette méthode comme identificateur d’objet unique à des fins de hachage. Deux conséquences sont les suivantes :

  • Vous ne devez pas supposer que les codes de hachage égaux impliquent l’égalité des objets.
  • Vous ne devez jamais conserver ou utiliser un code de hachage en dehors du domaine d’application dans lequel il a été créé, car le même objet peut se hacher entre les domaines d’application, les processus et les plateformes.

Avertissement

Un code de hachage est destiné à une insertion et une recherche efficaces dans les collections basées sur une table de hachage. Un code de hachage n’est pas une valeur permanente. Pour cette raison :

  • Ne sérialisez pas les valeurs de code de hachage ou stockez-les dans des bases de données.
  • N’utilisez pas le code de hachage comme clé pour récupérer un objet à partir d’une collection à clés.
  • N’envoyez pas de codes de hachage entre les domaines d’application ou les processus. Dans certains cas, les codes de hachage peuvent être calculés par processus ou par domaine d’application.
  • N’utilisez pas le code de hachage au lieu d’une valeur retournée par une fonction de hachage de chiffrement si vous avez besoin d’un hachage fort par chiffrement. Pour les hachages de chiffrement, utilisez une classe dérivée de la ou System.Security.Cryptography.KeyedHashAlgorithm de la System.Security.Cryptography.HashAlgorithm classe.
  • Ne testez pas l’égalité des codes de hachage pour déterminer si deux objets sont égaux. (Les objets inégaux peuvent avoir des codes de hachage identiques.) Pour tester l’égalité, appelez la ou Equals la ReferenceEquals méthode.

La GetHashCode méthode peut être substituée par un type dérivé. Si GetHashCode ce n’est pas le cas, les codes de hachage pour les types de référence sont calculés en appelant la Object.GetHashCode méthode de la classe de base, qui calcule un code de hachage basé sur la référence d’un objet ; pour plus d’informations, consultez RuntimeHelpers.GetHashCode. En d’autres termes, deux objets pour lesquels la ReferenceEquals méthode retourne true ont des codes de hachage identiques. Si les types valeur ne remplacent GetHashCodepas, la ValueType.GetHashCode méthode de la classe de base utilise la réflexion pour calculer le code de hachage en fonction des valeurs des champs du type. En d’autres termes, les types valeur dont les champs ont des valeurs égales ont des codes de hachage égaux. Pour plus d’informations sur la GetHashCodesubstitution, consultez la section « Notes à hériter ».

Avertissement

Si vous remplacez la GetHashCode méthode, vous devez également remplacer Equals, et vice versa. Si votre méthode substituée Equals retourne true lorsque deux objets sont testés pour l’égalité, votre méthode substituée GetHashCode doit retourner la même valeur pour les deux objets.

Si un objet utilisé comme clé dans une table de hachage ne fournit pas d’implémentation utile, GetHashCodevous pouvez spécifier un fournisseur de code de hachage en fournissant une IEqualityComparer implémentation à l’une des surcharges du Hashtable constructeur de classe.

Remarques relatives à Windows Runtime

Lorsque vous appelez la GetHashCode méthode sur une classe dans Windows Runtime, elle fournit le comportement par défaut pour les classes qui ne remplacent GetHashCodepas . Cela fait partie de la prise en charge que .NET fournit pour Windows Runtime (voir prise en charge de .NET pour les applications du Windows Store et Windows Runtime). Les classes dans Windows Runtime n’héritent pas et n’implémentent Objectpas actuellement un GetHashCode. Toutefois, ils semblent avoir ToString, Equals(Object)et GetHashCode les méthodes lorsque vous les utilisez dans votre code C# ou Visual Basic, et le .NET Framework fournit le comportement par défaut pour ces méthodes.

Remarque

Les classes Windows Runtime écrites en C# ou Visual Basic peuvent remplacer la GetHashCode méthode.

Exemples

L’une des méthodes les plus simples pour calculer un code de hachage pour une valeur numérique qui a la même plage ou une plage plus petite que le Int32 type consiste simplement à retourner cette valeur. L’exemple suivant montre une telle implémentation pour une Number structure.

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

Souvent, un type a plusieurs champs de données qui peuvent participer à la génération du code de hachage. Une façon de générer un code de hachage consiste à combiner ces champs à l’aide d’une XOR (eXclusive OR) opération, comme illustré dans l’exemple suivant.

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

L’exemple précédent retourne le même code de hachage pour (n1, n2) et (n2, n1), et peut donc générer plus de collisions que souhaitables. Un certain nombre de solutions sont disponibles afin que les codes de hachage dans ces cas ne soient pas identiques. L’une consiste à retourner le code de hachage d’un Tuple objet qui reflète l’ordre de chaque champ. L’exemple suivant montre une implémentation possible qui utilise la Tuple<T1,T2> classe. Notez toutefois que la surcharge de performances de l’instanciation d’un Tuple objet peut avoir un impact significatif sur les performances globales d’une application qui stocke un grand nombre d’objets dans des tables de hachage.

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

Une deuxième solution alternative consiste à pondérationr les codes de hachage individuels en déplaçant à gauche les codes de hachage des champs successifs par deux bits ou plus. De façon optimale, les bits décalés au-delà du bit 31 doivent être encapsulés au lieu d’être dis carte ed. Étant donné que les bits sont dis carte ed par les opérateurs de décalage gauche dans C# et Visual Basic, cela nécessite la création d’une méthode shift-and-wrap de gauche comme suit :

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

L’exemple suivant utilise ensuite cette méthode shift-and-wrap pour calculer le code de hachage de la Point structure utilisée dans les exemples précédents.

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