.NETのIEqualityComparer <T>におけるGetHashCodeの役割は何ですか?


142

インターフェイスIEqualityComparerのGetHashCodeメソッドの役割を理解しようとしています。

次の例は、MSDNから取得したものです。

using System;
using System.Collections.Generic;
class Example {
    static void Main() {
        try {

            BoxEqualityComparer boxEqC = new BoxEqualityComparer();

            Dictionary<Box, String> boxes = new Dictionary<Box,
                                                string>(boxEqC);

            Box redBox = new Box(4, 3, 4);
            Box blueBox = new Box(4, 3, 4);

            boxes.Add(redBox, "red");
            boxes.Add(blueBox, "blue");

            Console.WriteLine(redBox.GetHashCode());
            Console.WriteLine(blueBox.GetHashCode());
        }
        catch (ArgumentException argEx) {

            Console.WriteLine(argEx.Message);
        }
    }
}

public class Box {
    public Box(int h, int l, int w) {
        this.Height = h;
        this.Length = l;
        this.Width = w;
    }
    public int Height { get; set; }
    public int Length { get; set; }
    public int Width { get; set; }
}

class BoxEqualityComparer : IEqualityComparer<Box> {

    public bool Equals(Box b1, Box b2) {
        if (b1.Height == b2.Height & b1.Length == b2.Length
                            & b1.Width == b2.Width) {
            return true;
        }
        else {
            return false;
        }
    }

    public int GetHashCode(Box bx) {
        int hCode = bx.Height ^ bx.Length ^ bx.Width;
        return hCode.GetHashCode();
    }
}

Equalsメソッドの実装は、2つのBoxオブジェクトを比較するのに十分ではありませんか?ここで、オブジェクトの比較に使用されるルールをフレームワークに伝えます。なぜGetHashCodeが必要なのですか?

ありがとう。

ルシアン


en.wikipedia.org/wiki/Hash_tableを読んでから、GetHashCodeの目的をよりよく理解しているかどうかを確認してください。
消費者

1
この素晴らしい答えをご覧ください:stackoverflow.com/a/3719802/136967
ミハイル

回答:


201

最初に背景を少し...

.NETのすべてのオブジェクトには、EqualsメソッドとGetHashCodeメソッドがあります。

Equalsメソッドを使用して、1つのオブジェクトを別のオブジェクトと比較し、2つのオブジェクトが同等かどうかを確認します。

GetHashCodeメソッドは、オブジェクトの32ビット整数表現を生成します。オブジェクトに含めることができる情報量には制限がないため、特定のハッシュコードは複数のオブジェクトで共有されます。そのため、ハッシュコードは必ずしも一意である必要はありません。

ディクショナリは本当にクールなデータ構造で、追加/削除/取得操作の(多かれ少なかれ)一定のコストと引き換えに、より高いメモリフットプリントを交換します。ただし、繰り返し処理を行うにはあまり適していません。内部的には、ディクショナリには、値を格納できるバケットの配列が含まれています。キーと値をディクショナリに追加すると、キーに対してGetHashCodeメソッドが呼び出されます。返されるハッシュコードは、キーと値のペアを格納するバケットのインデックスを決定するために使用されます。

値にアクセスする場合は、キーを再度渡します。GetHashCodeメソッドがKeyで呼び出され、Valueを含むバケットが見つかります。

IEqualityComparerがディクショナリのコンストラクターに渡されると、Keyオブジェクトのメソッドではなく、IEqualityComparer.EqualsおよびIEqualityComparer.GetHashCodeメソッドが使用されます。

次に、両方の方法が必要な理由を説明するために、次の例を検討してください。

BoxEqualityComparer boxEqC = new BoxEqualityComparer(); 

Dictionary<Box, String> boxes = new Dictionary<Box, string>(boxEqC); 

Box redBox = new Box(100, 100, 25);
Box blueBox = new Box(1000, 1000, 25);

boxes.Add(redBox, "red"); 
boxes.Add(blueBox, "blue"); 

あなたの例でBoxEqualityComparer.GetHashCodeメソッドを使用すると、これらのボックスは両方とも同じオブジェクトではないにもかかわらず、同じハッシュコード-100 ^ 100 ^ 25 = 1000 ^ 1000 ^ 25 = 25-を持っています。この場合、それらが同じハッシュコードである理由は、^(ビット単位の排他的OR)演算子を使用しているため、100 ^ 100は、1000 ^ 1000の場合と同様に、ゼロを残すことをキャンセルします。2つの異なるオブジェクトが同じキーを持つ場合、それを衝突と呼びます。

同じハッシュコードを持つ2つのキーと値のペアをディクショナリに追加すると、両方が同じバケットに格納されます。したがって、Valueを取得する場合は、キーでGetHashCodeメソッドを呼び出してバケットを見つけます。バケットには複数の値があるため、ディクショナリはバケットのすべてのキー/値ペアを反復処理して、キーのEqualsメソッドを呼び出し、正しい値を見つけます。

投稿した例では、2つのボックスは同等であるため、Equalsメソッドはtrueを返します。この場合、辞書には2つの同一のキーがあるため、例外がスローされます。

TLDR

したがって、要約すると、GetHashCodeメソッドを使用して、オブジェクトが格納されているアドレスを生成します。したがって、辞書で検索する必要はありません。ハッシュコードを計算してその場所にジャンプするだけです。Equalsメソッドは同等性のより優れたテストですが、オブジェクトをアドレス空間にマップするために使用することはできません。


4
それらについては、^演算子とは何なのかと思いますが、これはビット単位の排他的OR演算子です。msdn.microsoft.com/en-us/library/zkacc7k1.aspxを参照してください
R. Schreurs 2013年

2
これを明示的に指摘するだけです:(msdn.microsoft.com/en-us/library/ms132155.aspx)実装者へのメモEqualsメソッドが2つのオブジェクトxとyに対してtrueを返す場合、返される値はGetHashCodeメソッドによるxの場合、yに返される値と同じである必要があります。
ディエゴフラー2013

2
@DiegoFrehner-その通りです。人をつまずかせる可能性があるもう1つのことは、オブジェクトが変更されてもGetHashCodeメソッドの値が変化しないことです。したがって、GetHashCodeが依存するオブジェクト内のフィールドは、読み取り専用(不変)である必要があります。ここに説明があります:stackoverflow.com/a/4868940/469701
sheikhjabootie 2013

1
@Acentric:オブジェクトのハッシュコードは、同等性に影響を与えるような方法で変更されない限り、変更しないでください。等式に影響を与えるような方法でクラスを変更できる場合、コードは、ディクショナリ内にある間にクラスを変更するコードに公開される可能性のあるインスタンスをディクショナリに格納しないようにする必要があります。オブジェクトを格納するコードがそのルールに従っている場合、変更可能な状態を反映するハッシュコードを使用すると便利です。残念ですが、.NETは状態の等価性と等価性を区別しません。どちらも有用な概念であるためです。
スーパーキャット2014

3
@Acentric:ハッシュテーブルのアドレッシングにハッシュコードを使用する以外にも、ハッシュコードの背後にある基本的な考え方は、2つのオブジェクトに異なるハッシュコードがあるという知識は、それらが等しくなく、それらを比較する必要がないことを意味します。当然の結果として、多くのオブジェクトのハッシュコードが特定のオブジェクトのハッシュコードと一致しないという知識は、それらのどれもオブジェクトと等しくないことを意味します。アドレス指定にハッシュコードを使用することは、基本的に、異なるハッシュコードを持つオブジェクトを無視する方法です。
スーパーキャット2014

9

GetHashCodeはディクショナリコレクションで使用され、オブジェクトを格納するためのハッシュを作成します。IEqualtyComparerGetHashCode を使用する理由と方法については、http://dotnetperls.com/iequalitycomparerの記事をご覧ください。


4
詳細:Equalsを比較する必要がある場合はenoufですが、Dictionaryから要素を取得する必要がある場合は、Equalsを使用するのではなく、ハッシュでこれを行う方が簡単です。
Ash

5

a およびDictionary<TKey,TValue>そのGetValue類似のメソッドがEquals、格納されているすべてのキーを呼び出して、それが目的のキーと一致するかどうかを確認することは可能ですが、非常に時間がかかります。代わりに、多くのハッシュベースのコレクションと同様に、GetHashCode一致しないほとんどの値を迅速に検討対象から除外します。GetHashCode求められているアイテムを呼び出すと42が得られ、コレクションには53,917のアイテムがありますが、GetHashCode53,914を呼び出すと42以外の値が得られる場合、求められているものと比較する必要があるのは3アイテムだけです。他の53,914は無視しても問題ありません。

a GetHashCodeがa に含まれている理由はIEqualityComparer<T>、辞書のコンシューマが、通常互いに等しいと見なさない、等しいオブジェクトと見なしたいと思う可能性を許容するためです。最も一般的な例は、文字列をキーとして使用したいが、大文字と小文字を区別しない比較を使用する呼び出し元です。これを効率的に機能させるために、ディクショナリには、「Fox」と「FOX」に同じ値を生成する何らかのハッシュ関数が必要ですが、「ボックス」または「ゼブラ」には別のものが生成されると期待されます。GetHashCode組み込みのメソッドStringはそのように機能しないため、ディクショナリは別の場所からそのようなメソッドを取得する必要があります。IEqualityComparer<T>Equals 「Fox」と「FOX」は互いに同一であると見なしますが、「ボックス」または「ゼブラ」とは見なしません。


質問に対する正しい答えが正解です!GetHashCode()は、問題のオブジェクトのEquals()を補完する必要があります。
Sumith

@スミス:ハッシュについての多くの議論はバケットについて語っていますが、除外について考えることはより有用だと思います。比較にコストがかかる場合、バケットに編成されていないコレクションを使用している場合でも、ハッシュによってメリットが得られる可能性があります。
スーパーキャット'10 / 10/19
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.