Compartir a través de


Método System.Object.GetHashCode

En este artículo se proporcionan comentarios adicionales a la documentación de referencia de esta API.

El GetHashCode método proporciona un código hash para algoritmos que necesitan comprobaciones rápidas de la igualdad de objetos. Un código hash es un valor numérico que se usa para insertar e identificar un objeto en una colección basada en hash, como la Dictionary<TKey,TValue> clase, la Hashtable clase o un tipo derivado de la DictionaryBase clase .

Nota:

Para obtener información sobre cómo se usan los códigos hash en tablas hash y para algunos algoritmos de código hash adicionales, vea la entrada función hash en Wikipedia.

Dos objetos que son iguales a códigos hash devueltos que son iguales. Sin embargo, la inversa no es cierta: los códigos hash iguales no implican la igualdad de objetos, ya que los distintos objetos (distintos) pueden tener códigos hash idénticos. Además, .NET no garantiza la implementación predeterminada del GetHashCode método y el valor que devuelve este método puede diferir entre las implementaciones de .NET, como versiones diferentes de .NET Framework y .NET Core, y plataformas, como plataformas de 32 y 64 bits. Por estos motivos, no use la implementación predeterminada de este método como identificador de objeto único con fines hash. A continuación se muestran dos consecuencias:

  • No debe suponer que los códigos hash iguales implican la igualdad de objetos.
  • Nunca debe conservar o usar un código hash fuera del dominio de aplicación en el que se creó, ya que el mismo objeto puede aplicar un hash entre dominios de aplicación, procesos y plataformas.

Advertencia

Un código hash está diseñado para una inserción y búsqueda eficaces en colecciones basadas en una tabla hash. Un código hash no es un valor permanente. Por este motivo:

  • No serialice los valores de código hash ni los almacene en las bases de datos.
  • No use el código hash como clave para recuperar un objeto de una colección con claves.
  • No envíe códigos hash entre dominios de aplicación ni procesos. En algunos casos, los códigos hash se pueden calcular por proceso o por dominio de aplicación.
  • No use el código hash en lugar de un valor devuelto por una función hash criptográfica si necesita un hash criptográficomente seguro. Para los hash criptográficos, use una clase derivada de la System.Security.Cryptography.HashAlgorithm clase o System.Security.Cryptography.KeyedHashAlgorithm .
  • No pruebe la igualdad de códigos hash para determinar si dos objetos son iguales. (Los objetos desiguales pueden tener códigos hash idénticos). Para probar la igualdad, llame al ReferenceEquals método o Equals .

Un tipo derivado puede invalidar el GetHashCode método . Si GetHashCode no se invalida, los códigos hash de los tipos de referencia se calculan llamando al Object.GetHashCode método de la clase base, que calcula un código hash basado en la referencia de un objeto; para obtener más información, vea RuntimeHelpers.GetHashCode. Es decir, dos objetos para los que el ReferenceEquals método devuelve true tienen códigos hash idénticos. Si los tipos de valor no invalidan GetHashCode, el ValueType.GetHashCode método de la clase base usa la reflexión para calcular el código hash en función de los valores de los campos del tipo. En otras palabras, los tipos de valor cuyos campos tienen valores iguales tienen códigos hash iguales. Para obtener más información sobre cómo reemplazar GetHashCode, vea la sección "Notes to Inheritors" (Notas para heredar).

Advertencia

Si invalida el GetHashCode método , también debe invalidar Equalsy viceversa. Si el método invalidado Equals devuelve true cuando se prueban dos objetos para obtener igualdad, el método invalidado GetHashCode debe devolver el mismo valor para los dos objetos.

Si un objeto que se usa como clave en una tabla hash no proporciona una implementación útil de GetHashCode, puede especificar un proveedor de código hash proporcionando una IEqualityComparer implementación a una de las sobrecargas del Hashtable constructor de clases.

Notas de Windows Runtime

Cuando llamas al GetHashCode método en una clase de Windows Runtime, proporciona el comportamiento predeterminado para las clases que no invalidan GetHashCode. Esto forma parte de la compatibilidad que .NET proporciona para Windows Runtime (consulte Compatibilidad de .NET con aplicaciones de la Tienda Windows y Windows Runtime). Las clases de Windows Runtime no heredan Objecty actualmente no implementan .GetHashCode Sin embargo, parecen tener ToStringmétodos , Equals(Object)y GetHashCode cuando se usan en el código de C# o Visual Basic, y .NET Framework proporciona el comportamiento predeterminado para estos métodos.

Nota:

Las clases de Windows Runtime escritas en C# o Visual Basic pueden invalidar el GetHashCode método .

Ejemplos

Una de las formas más sencillas de calcular un código hash para un valor numérico que tiene el mismo o un intervalo menor que el Int32 tipo es simplemente devolver ese valor. En el ejemplo siguiente se muestra una implementación de este tipo para una Number estructura.

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

Con frecuencia, un tipo tiene varios campos de datos que pueden participar en la generación del código hash. Una manera de generar un código hash es combinar estos campos mediante una XOR (eXclusive OR) operación, como se muestra en el ejemplo siguiente.

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

En el ejemplo anterior se devuelve el mismo código hash para (n1, n2) y (n2, n1), por lo que puede generar más colisiones de las que son deseables. Hay una serie de soluciones disponibles para que los códigos hash en estos casos no sean idénticos. Uno es devolver el código hash de un Tuple objeto que refleja el orden de cada campo. En el ejemplo siguiente se muestra una posible implementación que usa la Tuple<T1,T2> clase . Sin embargo, tenga en cuenta que la sobrecarga de rendimiento de crear instancias de un Tuple objeto puede afectar significativamente al rendimiento general de una aplicación que almacena un gran número de objetos en tablas 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

Una segunda solución alternativa implica ponderar los códigos hash individuales cambiando a la izquierda los códigos hash de campos sucesivos por dos o más bits. De forma óptima, los bits desplazados más allá del bit 31 deben encapsularse en lugar de descartarse. Dado que los operadores de desplazamiento a la izquierda descartan bits en C# y Visual Basic, esto requiere crear un método de desplazamiento y ajuste a la izquierda como el siguiente:

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

A continuación, en el ejemplo siguiente se usa este método shift-and-wrap para calcular el código hash de la Point estructura usada en los ejemplos anteriores.

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