オブジェクトのフィールドを汎用ディクショナリキーとして使用する


120

オブジェクトをのキーとして使用したい場合Dictionary、特定の方法でオブジェクトを比較するには、どのメソッドをオーバーライドする必要がありますか?

プロパティを持つクラスがあるとします:

class Foo {
    public string Name { get; set; }
    public int FooID { get; set; }

    // elided
} 

そして私は作りたいです:

Dictionary<Foo, List<Stuff>>

私が欲しいFooと同じとオブジェクトがFooID同じグループ検討します。Fooクラスでどのメソッドをオーバーライドする必要がありますか?

まとめるとStuffFooオブジェクトをオブジェクト別にグループ化したリストに分類したいと思います。Stuffオブジェクトには、FooIDそれらをカテゴリにリンクするためのa があります。

回答:


151

デフォルトでは、2つの重要なメソッドはGetHashCode()およびEquals()です。2つのものが等しい(Equals()trueを返す)場合、それらは同じハッシュコードを持つことが重要です。たとえば、「FooIDを返す」とします。GetHashCode()あなたは試合のようにそれをしたい場合。を実装することもできますがIEquatable<Foo>、これはオプションです。

class Foo : IEquatable<Foo> {
    public string Name { get; set;}
    public int FooID {get; set;}

    public override int GetHashCode() {
        return FooID;
    }
    public override bool Equals(object obj) {
        return Equals(obj as Foo);
    }
    public bool Equals(Foo obj) {
        return obj != null && obj.FooID == this.FooID;
    }
}

最後に、もう1つの方法はIEqualityComparer<T>、同じことを行うを提供することです。


5
+1して、このスレッドをハイジャックするつもりはありませんが、GetHashCode()がFooId.GetHashCode()を返すべきだという印象を受けました。これは正しいパターンではありませんか?
ケンブラウニング

8
@ケン-まあ、それは必要な機能を提供するintを返す必要があるだけです。どのFooIDがFooID.GetHashCode()と同じように機能します。実装の詳細として、Int32.GetHashCode()は「これを返す」です。他のタイプ(文字列など)の場合、はい:.GetHashCode()は非常に便利です。
Marc Gravell

2
ありがとう!IEqualityComparer <T>を使用したのは、オーバーライドされたメソッドが必要なのはDicarionaryだけだったからです。
ダナ

1
ハッシュテーブル(Dictionary <T>、Dictionary、HashTableなど)に基づくコンテナーのパフォーマンスは、使用されるハッシュ関数の品質に依存することに注意してください。ハッシュコードとして単にFooIDを使用すると、コンテナのパフォーマンスが非常に低下する可能性があります。
ヨルゲンFogh

2
@JørgenFogh私はそれをよく知っています。提示された例は、述べられた意図と一致しています。ハッシュの不変性に関連する多くの懸念事項があります。idは名前ほど頻繁に変更されず、通常は確実に一意であり、同等であることを示します。しかし、重要な主題です。
Marc Gravell

33

FooIDグループの識別子にしたいので、Fooオブジェクトの代わりにそれを辞書のキーとして使用する必要があります。

Dictionary<int, List<Stuff>>

あなたが使うなら Fooオブジェクトをキーとして、GetHashCodeand Equalsメソッドを実装して、FooIDプロパティのみを考慮するようにします。Nameプロパティは、ちょうど限り自重だろうDictionaryあなただけ使用するので、心配していたFooのラッパーとしてint

したがって、FooID値を直接使用することをお勧めします。その後、キーとしてのDictionary使用をすでにサポートしているintため、何も実装する必要はありません。

編集:
Fooとにかくクラスをキーとして 使用したい場合IEqualityComparer<Foo>は、簡単に実装できます。

public class FooEqualityComparer : IEqualityComparer<Foo> {
   public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); }
   public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; }
}

使用法:

Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());

1
より正確には、intは、キーとして使用するために必要なメソッド/インターフェースをすでにサポートしています。ディクショナリには、intまたはその他のタイプの直接的な知識はありません。
ジムミッシェル

私はそれについて考えましたが、さまざまな理由から、オブジェクトを辞書のキーとして使用するほうがよりクリーンで便利でした。
Dana、

1
まあ、実際にはidをキーとして使用しているだけなので、オブジェクトをキーとして使用しているように見えます。
Guffa

8

Fooの場合、object.GetHashCode()およびobject.Equals()をオーバーライドする必要があります。

辞書はGetHashCode()を呼び出して各値のハッシュバケットを計算し、Equalsを呼び出して2つのFooが同一かどうかを比較します。

適切なハッシュコードを計算してください(同じハッシュコードを持つ多くの等しいFooオブジェクトを避けてください)。ただし、2つの等しいFoosが同じハッシュコードを持っていることを確認してください。Equals-Methodから始めて、次に(GetHashCode()で)xorで比較するすべてのメンバーのハッシュコードをxorしたい場合があります。

public class Foo { 
     public string A;
     public string B;

     override bool Equals(object other) {
          var otherFoo = other as Foo;
          if (otherFoo == null)
             return false;
          return A==otherFoo.A && B ==otherFoo.B;
     }

     override int GetHashCode() {
          return 17 * A.GetHashCode() + B.GetHashCode();
     }
}

2
余談ですが、xor(^)は、多くの対角線上の衝突({"foo"、 "bar"}対{"bar"、 "foo"})につながることが多いため、ハッシュコードのコンビネーターとしては不十分です。選択は、各項を乗算して追加することです。つまり、17 * a.GetHashCode()+ B.GetHashCode();
Marc Gravell

2
マルク、私はあなたが何を意味するかを理解しています。しかし、どのようにしてマジックナンバー17に到達しますか?ハッシュを組み合わせるための乗数として素数を使用することは有利ですか?もしそうなら、なぜですか?
froh42 2009年

次を返すことをお勧めします:(A + B).GetHashCode()ではなく:17 * A.GetHashCode()+ B.GetHashCode()これは、1)衝突する可能性が低くなり、2)ないことを確認します整数オーバーフロー。
Charles Burns

(A + B).GetHashCode()は、(A、B)の異なるセットが同じ文字列に連結されている場合、同じハッシュになる可能性があるため、非常に悪いハッシュアルゴリズムになります。「中空」+「ネッド」は「地獄」+「所有」と同じで、同じハッシュになります。
kaesve 2014年

@kaesve(A + "" + B).GetHashCode()はどうですか?
タイムレス、2014年

1

Hashtableクラスはどうですか!

Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
{
   // Do your job
}

上記の方法で、任意のオブジェクト(クラスオブジェクト)を汎用ディクショナリキーとして使用できます:)


1

私も同じ問題を抱えていました。EqualsとGetHashCodeをオーバーライドすることで、キーとして試したオブジェクトを使用できるようになりました。

以下は、Equals(object obj)とGetHashCode()のオーバーライドの内部で使用するメソッドで作成したクラスです。私は、ほとんどのオブジェクトをカバーできるはずのジェネリックとハッシュアルゴリズムを使用することにしました。オブジェクトの種類によっては機能しないものがあり、それを改善する方法がある場合は、お知らせください。

public class Equality<T>
{
    public int GetHashCode(T classInstance)
    {
        List<FieldInfo> fields = GetFields();

        unchecked
        {
            int hash = 17;

            foreach (FieldInfo field in fields)
            {
                hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
            }
            return hash;
        }
    }

    public bool Equals(T classInstance, object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (classInstance.GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(classInstance, (T)obj);
    }

    private bool Equals(T classInstance, T otherInstance)
    {
        List<FieldInfo> fields = GetFields();

        foreach (var field in fields)
        {
            if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
            {
                return false;
            }
        }

        return true;
    }

    private List<FieldInfo> GetFields()
    {
        Type myType = typeof(T);

        List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList();
        return fields;
    }
}

クラスでの使用方法は次のとおりです。

public override bool Equals(object obj)
    {
        return new Equality<ClassName>().Equals(this, obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.