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