Object.GetHashCode Метод

Определение

Служит хэш-функцией по умолчанию.

public:
 virtual int GetHashCode();
public virtual int GetHashCode ();
abstract member GetHashCode : unit -> int
override this.GetHashCode : unit -> int
Public Overridable Function GetHashCode () As Integer

Возвращаемое значение

Хэш-код для текущего объекта.

Примеры

Один из самых простых способов вычислить хэш-код для числового значения, которое имеет тот же или меньший диапазон, чем Int32 тип, — просто вернуть это значение. В следующем примере показана такая реализация структуры 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 Example
{
   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 Example
   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

Часто тип содержит несколько полей данных, которые могут участвовать в создании хэш-кода. Одним из способов создания хэш-кода является объединение этих полей с помощью XOR (eXclusive OR) операции, как показано в следующем примере.

using System;

// A type that represents a 2-D point.
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 x ^ y;
    }
}

public class Example
{
   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:
//       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 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 x Xor y
    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

В предыдущем примере возвращается тот же хэш-код для (n1, n2) и (n2, n1), поэтому может возникнуть больше конфликтов, чем желательно. Существует ряд решений, поэтому хэш-коды в таких случаях не идентичны. Один из них — возврат хэш-кода Tuple объекта, отражающего порядок каждого поля. В следующем примере показана возможная реализация, использующая Tuple<T1,T2> класс . Обратите внимание, что издержки на производительность при создании экземпляра Tuple объекта могут значительно повлиять на общую производительность приложения, в котором хранится большое количество объектов в хэш-таблицах.

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)
        {
            Point p = (Point) 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()
   {
        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:
//       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

Второе альтернативное решение включает в себя взвешивание отдельных хэш-кодов путем сдвига хэш-кодов последовательных полей влево на два или более бита. В оптимальном случае биты, сдвинутые за бит 31, должны быть обтекаемы, а не отбрасываются. Так как биты удаляются операторами сдвига влево в C# и Visual Basic, для этого необходимо создать метод shift-and-wrap влево, как показано ниже:

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

В следующем примере этот метод shift-and-wrap используется для вычисления хэш-кода структуры, используемой Point в предыдущих примерах.

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 Example
{
   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 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 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 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:
'       28
'       37

Комментарии

Хэш-код — это числовое значение, которое используется для вставки и идентификации объекта в хэш-коллекции, такой как Dictionary<TKey,TValue> класс, Hashtable класс или тип, производный DictionaryBase от класса . Метод GetHashCode предоставляет этот хэш-код для алгоритмов, которым требуется быстрая проверка равенства объектов.

Примечание

Сведения об использовании хэш-кодов в хэш-таблицах и о некоторых дополнительных алгоритмах хэш-кода см. в статье Хэш-функция в Википедии.

Два объекта, которые равны, возвращают одинаковые хэш-коды. Однако обратное не верно: одинаковые хэш-коды не подразумевают равенство объектов, так как разные (неравные) объекты могут иметь одинаковые хэш-коды. Кроме того, .NET не гарантирует реализацию GetHashCode метода по умолчанию, и значение, возвращаемое этим методом, может отличаться для реализаций .NET, таких как разные версии платформа .NET Framework и .NET Core, и платформ, таких как 32-разрядные и 64-разрядные платформы. По этим причинам не используйте реализацию этого метода по умолчанию в качестве уникального идентификатора объекта для целей хэширования. Отсюда следуют два последствия:

  • Не следует предполагать, что одинаковые хэш-коды подразумевают равенство объектов.

  • Никогда не следует сохранять или использовать хэш-код за пределами домена приложения, в котором он был создан, так как один и тот же объект может хэшировать в доменах приложений, процессах и платформах.

Предупреждение

Хэш-код предназначен для эффективной вставки и поиска в коллекциях, основанных на хэш-таблице. Хэш-код не является постоянным значением. По этой причине:

  • Не сериализуйте значения хэш-кода и не храните их в базах данных.
  • Не используйте хэш-код в качестве ключа для получения объекта из коллекции с ключами.
  • Не отправляйте хэш-коды между доменами приложений или процессами. В некоторых случаях хэш-коды могут вычисляться для каждого процесса или домена приложения.
  • Не используйте хэш-код вместо значения, возвращаемого функцией криптографического хэширования, если требуется криптографически надежный хэш. Для криптографических хэшей используйте класс, производный System.Security.Cryptography.HashAlgorithm от класса или System.Security.Cryptography.KeyedHashAlgorithm .
  • Не проверяйте равенство хэш-кодов, чтобы определить, равны ли два объекта. (Неравные объекты могут иметь идентичные хэш-коды.) Чтобы проверить равенство, вызовите ReferenceEquals метод или Equals .

Метод GetHashCode может быть переопределен производным типом. Если GetHashCode параметр не переопределен, хэш-коды для ссылочных типов вычисляются путем вызова Object.GetHashCode метода базового класса, который вычисляет хэш-код на основе ссылки на объект. Дополнительные сведения см. в разделе RuntimeHelpers.GetHashCode. Иными словами, два объекта, для которых ReferenceEquals возвращает true метод, имеют одинаковые хэш-коды. Если типы значений не переопределяют GetHashCode, ValueType.GetHashCode метод базового класса использует отражение для вычисления хэш-кода на основе значений полей типа. Другими словами, типы значений, поля которых имеют одинаковые значения, имеют одинаковые хэш-коды. Дополнительные сведения о переопределении GetHashCodeсм. в разделе "Примечания к наследующим".

Предупреждение

При переопределении GetHashCode метода следует также переопределить Equals, и наоборот. Если переопределенный Equals метод возвращает значение true , когда два объекта проверяются на равенство, переопределенный GetHashCode метод должен возвращать одинаковое значение для двух объектов.

Если объект, используемый в качестве ключа в хэш-таблице, не предоставляет полезную реализацию GetHashCode, можно указать поставщик хэш-кода, предоставив IEqualityComparer реализацию одной из перегрузок конструктора Hashtable класса.

Примечания для среда выполнения Windows

При вызове GetHashCode метода для класса в среда выполнения Windows он обеспечивает поведение по умолчанию для классов, которые не переопределяют GetHashCode. Это часть поддержки, которую платформа .NET Framework предоставляет для среда выполнения Windows (см. раздел Поддержка платформа .NET Framework приложений Магазина Windows и среда выполнения Windows). Классы в среда выполнения Windows не наследуют Objectи в настоящее время не реализуют GetHashCode. Однако они, как представляется, имеют ToStringметоды , Equals(Object)и GetHashCode при их использовании в коде C# или Visual Basic, а платформа .NET Framework обеспечивает поведение по умолчанию для этих методов.

Примечание

среда выполнения Windows классы, написанные на C# или Visual Basic, могут переопределить GetHashCode метод .

Примечания для тех, кто наследует этот метод

Хэш-функция используется для быстрого создания числа (хэш-кода), соответствующего значению объекта. Хэш-функции обычно относятся к каждому типу и для уникальности должны использовать по крайней мере одно из полей экземпляра в качестве входных данных. Хэш-коды не должны вычисляться с использованием значений статических полей.

Для классов, производных от Object, GetHashCode метод может делегировать реализацию базового класса GetHashCode() только в том случае, если производный класс определяет равенство как равенство ссылок. Реализация GetHashCode() по умолчанию для ссылочных типов возвращает хэш-код, эквивалентный тому, который возвращается методом GetHashCode(Object) . Для неизменяемых ссылочных типов можно переопределить GetHashCode() . Как правило, для изменяемых ссылочных типов следует переопределять GetHashCode() только в следующих случаях:

  • Вы можете вычислить хэш-код из полей, которые не являются изменяемыми; Или

  • Вы можете убедиться, что хэш-код изменяемого объекта не изменяется, пока объект содержится в коллекции, которая зависит от его хэш-кода.

В противном случае можно подумать, что изменяемый объект теряется в хэш-таблице. Если вы решили переопределить GetHashCode() для изменяемого ссылочного типа, в документации должно быть ясно, что пользователи вашего типа не должны изменять значения объектов, пока объект хранится в хэш-таблице.

Для типов значений предоставляет реализацию хэш-кода по умолчанию, GetHashCode() которая использует отражение. Для повышения производительности рекомендуется переопределить его.

Дополнительные сведения и примеры вычисления хэш-кодов различными способами см. в разделе Примеры.

Хэш-функция должна иметь следующие свойства:

  • Если два объекта сравниваются как равные GetHashCode() , метод для каждого объекта должен возвращать одно и то же значение. Однако если два объекта не сравниваются как равные, GetHashCode() методы для этих двух объектов не должны возвращать разные значения.

  • Метод GetHashCode() для объекта должен постоянно возвращать один и тот же хэш-код, если не будет изменено состояние объекта, определяющее возвращаемое значение метода System.Object.Equals объекта. Обратите внимание, что это верно только для текущего выполнения приложения и что при повторном запуске приложения может быть возвращен другой хэш-код.

  • Для максимальной производительности хэш-функция должна генерировать равномерное распределение для всех входных данных, включая входные данные, которые сильно кластеризованы. Подразумевается, что небольшие изменения состояния объекта должны привести к большим изменениям результирующего хэш-кода для обеспечения оптимальной производительности хэш-таблицы.

  • Хэш-функции должны быть недорогими для вычислений.

  • Метод GetHashCode() не должен вызывать исключения.

Например, реализация метода, предоставляемого GetHashCode() классом String , возвращает идентичные хэш-коды для идентичных строковых значений. Таким образом, два String объекта возвращают один и тот же хэш-код, если они представляют одно и то же строковое значение. Кроме того, метод использует все символы в строке для создания разумного случайного распределения выходных данных, даже если входные данные кластеризованы в определенных диапазонах (например, у многих пользователей могут быть строки, содержащие только менее 128 символов ASCII, даже если строка может содержать любой из 65 535 символов Юникода).

Предоставление хорошей хэш-функции для класса может значительно повлиять на производительность добавления этих объектов в хэш-таблицу. В хэш-таблице с ключами, обеспечивающими хорошую реализацию хэш-функции, поиск элемента занимает постоянное время (например, операция O(1). В хэш-таблице с плохой реализацией хэш-функции производительность поиска зависит от количества элементов в хэш-таблице (например, операция O(n), где n — количество элементов в хэш-таблице). Злоумышленник может вводить данные, которые увеличивают число конфликтов, что может значительно снизить производительность приложений, зависящих от хэш-таблиц, при следующих условиях:

  • Если хэш-функции создают частые конфликты.

  • Если большая часть объектов в хэш-таблице создает хэш-коды, равные или приблизительно равные друг другу.

  • Когда пользователи вводили данные, из которых вычисляется хэш-код.

Производные классы, которые переопределяют GetHashCode() , также должны переопределяться Equals(Object) , чтобы гарантировать, что два объекта, которые считаются равными, имеют одинаковый хэш-код; в Hashtable противном случае тип может работать неправильно.

Применяется к

См. также раздел