この記事では、この API のリファレンス ドキュメントに補足的な解説を提供します。
GetHashCode メソッドは、オブジェクトの等価性をすばやくチェックする必要があるアルゴリズムのハッシュ コードを提供します。 ハッシュ コードは、 Dictionary<TKey,TValue> クラス、 Hashtable クラス、 DictionaryBase クラスから派生した型など、ハッシュ ベースのコレクション内のオブジェクトを挿入および識別するために使用される数値です。
注
ハッシュ テーブルでハッシュ コードを使用する方法と、いくつかの追加のハッシュ コード アルゴリズムについては、Wikipedia の ハッシュ関数 エントリを参照してください。
等しい 2 つのオブジェクトは、等しいハッシュ コードを返します。 ただし、逆は正しくありません。等しいハッシュ コードは、異なる (等しくない) オブジェクトが同じハッシュ コードを持つ可能性があるため、オブジェクトの等価性を意味しません。 さらに、.NET では、 GetHashCode メソッドの既定の実装は保証されません。このメソッドが返す値は、.NET Framework と .NET Core の異なるバージョンなどの .NET 実装と、32 ビットプラットフォームや 64 ビット プラットフォームなどのプラットフォームによって異なる場合があります。 このような理由から、このメソッドの既定の実装は、ハッシュの目的で一意のオブジェクト識別子として使用しないでください。 その結果、次の 2 つの結果が得まれます。
- 等しいハッシュ コードがオブジェクトの等価性を意味するとは想定しないでください。
- 同じオブジェクトがアプリケーション ドメイン、プロセス、プラットフォーム間でハッシュされる可能性があるため、作成されたアプリケーション ドメインの外部でハッシュ コードを保持したり使用したりしないでください。
Warnung
ハッシュ コードは、ハッシュ テーブルに基づくコレクション内での効率的な挿入と検索を目的としています。 ハッシュ コードは永続的な値ではありません。 このため、次のことが行われます。
- ハッシュ コード値をシリアル化したり、データベースに格納したりしないでください。
- キー付きコレクションからオブジェクトを取得するキーとしてハッシュ コードを使用しないでください。
- アプリケーション ドメインまたはプロセス間でハッシュ コードを送信しないでください。 場合によっては、ハッシュ コードがプロセスごとまたはアプリケーション ドメインごとに計算される場合があります。
- 暗号的に強力なハッシュが必要な場合は、暗号化ハッシュ関数によって返される値の代わりにハッシュ コードを使用しないでください。 暗号化ハッシュの場合は、 System.Security.Cryptography.HashAlgorithm または System.Security.Cryptography.KeyedHashAlgorithm クラスから派生したクラスを使用します。
- ハッシュ コードの等価性をテストして、2 つのオブジェクトが等しいかどうかを判断しないでください。 (等しくないオブジェクトは同じハッシュ コードを持つことができます)。等しいかどうかをテストするには、 ReferenceEquals メソッドまたは Equals メソッドを呼び出します。
GetHashCode メソッドは、派生型によってオーバーライドできます。
GetHashCodeがオーバーライドされない場合、参照型のハッシュ コードは、オブジェクトの参照に基づいてハッシュ コードを計算する基底クラスのObject.GetHashCode メソッドを呼び出すことによって計算されます。詳細については、RuntimeHelpers.GetHashCodeを参照してください。 つまり、 ReferenceEquals メソッドが返す 2 つのオブジェクト true
同じハッシュ コードを持ちます。 値型が GetHashCodeをオーバーライドしない場合、基底クラスの ValueType.GetHashCode メソッドはリフレクションを使用して、型のフィールドの値に基づいてハッシュ コードを計算します。 言い換えると、フィールドの値が等しい値を持つ値型は、等しいハッシュ コードを持ちます。
GetHashCodeのオーバーライドの詳細については、「継承元へのメモ」セクションを参照してください。
Warnung
GetHashCode メソッドをオーバーライドする場合は、Equalsもオーバーライドする必要があります。その逆も同様です。 2 つのオブジェクトが等しいかどうかをテストしたときにオーバーライドされた Equals メソッドが true
を返す場合、オーバーライドされた GetHashCode メソッドは、2 つのオブジェクトに対して同じ値を返す必要があります。
ハッシュ テーブルのキーとして使用されるオブジェクトがGetHashCodeの便利な実装を提供しない場合は、IEqualityComparer クラス コンストラクターのいずれかのオーバーロードにHashtable実装を指定することで、ハッシュ コード プロバイダーを指定できます。
Windows ランタイムに関する注意事項
Windows ランタイムのクラスに対して GetHashCode メソッドを呼び出すと、 GetHashCodeをオーバーライドしないクラスの既定の動作が提供されます。 これは、Windows ランタイムに対して .NET が提供するサポートの一部です (Windows ストア アプリと Windows ランタイムの .NET サポートを参照)。 Windows ランタイムのクラスは Objectを継承せず、現在は GetHashCodeを実装していません。 ただし、C# または Visual Basic コードでメソッドを使用すると、 ToString、 Equals(Object)、および GetHashCode メソッドが含まれるように見えます。.NET Framework では、これらのメソッドの既定の動作が提供されます。
注
C# または Visual Basic で記述された Windows ランタイム クラスは、 GetHashCode メソッドをオーバーライドできます。
例示
Int32型と同じまたは小さい範囲の数値のハッシュ コードを計算する最も簡単な方法の 1 つは、単にその値を返す方法です。 次の例は、 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 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
多くの場合、型には、ハッシュ コードの生成に関与できる複数のデータ フィールドがあります。 ハッシュ コードを生成する方法の 1 つは、次の例に示すように、 XOR (eXclusive OR)
操作を使用してこれらのフィールドを結合することです。
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
前の例では、(n1,n2) と (n2, n1) に対して同じハッシュ コードが返されるため、望ましいよりも多くの競合が発生する可能性があります。 このような場合のハッシュ コードが同一でないように、多くのソリューションを利用できます。 1 つは、各フィールドの順序を反映する Tuple
オブジェクトのハッシュ コードを返す方法です。 次の例は、 Tuple<T1,T2> クラスを使用する可能性のある実装を示しています。 ただし、 Tuple
オブジェクトをインスタンス化するパフォーマンス オーバーヘッドは、多数のオブジェクトをハッシュ テーブルに格納するアプリケーションの全体的なパフォーマンスに大きな影響を与える可能性があることに注意してください。
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
2 つ目の代替ソリューションでは、連続するフィールドのハッシュ コードを 2 ビット以上左シフトして、個々のハッシュ コードを重み付けします。 理想的には、ビット 31 を超えてシフトされたビットは、破棄されるのではなく循環します。 ビットは C# と Visual Basic の両方で左シフト演算子によって破棄されるため、次のような左シフトおよびラップ メソッドを作成する必要があります。
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 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
.NET