web-dev-qa-db-ja.com

ジェネリック配列を含むオブジェクトのGetHashCodeオーバーライド

次の2つのプロパティを含むクラスがあります。

public int Id      { get; private set; }
public T[] Values  { get; private set; }

できましたIEquatable<T>およびobject.Equals このような:

public override bool Equals(object obj)
{
    return Equals(obj as SimpleTableRow<T>);
}

public bool Equals(SimpleTableRow<T> other)
{
    // Check for null
    if(ReferenceEquals(other, null))
        return false;

    // Check for same reference
    if(ReferenceEquals(this, other))
        return true;

    // Check for same Id and same Values
    return Id == other.Id && Values.SequenceEqual(other.Values);
}

オーバーライドobject.EqualsもちろんGetHashCodeもオーバーライドする必要があります。しかし、どのコードを実装する必要がありますか?汎用配列からハッシュコードを作成するにはどうすればよいですか?そして、それをId整数とどのように組み合わせるのですか?

public override int GetHashCode()
{
    return // What?
}
54
Svish

このスレッドで発生した問題のため、間違った場合に何が起こるかを示す別の返信を投稿しています...主に、配列のGetHashCode();を使用することはできません。正しい動作は、実行時に警告が出力されないことです...それを修正するためにコメントを切り替えます:

using System;
using System.Collections.Generic;
using System.Linq;
static class Program
{
    static void Main()
    {
        // first and second are logically equivalent
        SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6),
            second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6);

        if (first.Equals(second) && first.GetHashCode() != second.GetHashCode())
        { // proven Equals, but GetHashCode() disagrees
            Console.WriteLine("We have a problem");
        }
        HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>();
        set.Add(first);
        set.Add(second);
        // which confuses anything that uses hash algorithms
        if (set.Count != 1) Console.WriteLine("Yup, very bad indeed");
    }
}
class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>>
{

    public SimpleTableRow(int id, params T[] values) {
        this.Id = id;
        this.Values = values;
    }
    public int Id { get; private set; }
    public T[] Values { get; private set; }

    public override int GetHashCode() // wrong
    {
        return Id.GetHashCode() ^ Values.GetHashCode();
    }
    /*
    public override int GetHashCode() // right
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }
    */
    public override bool Equals(object obj)
    {
        return Equals(obj as SimpleTableRow<T>);
    }
    public bool Equals(SimpleTableRow<T> other)
    {
        // Check for null
        if (ReferenceEquals(other, null))
            return false;

        // Check for same reference
        if (ReferenceEquals(this, other))
            return true;

        // Check for same Id and same Values
        return Id == other.Id && Values.SequenceEqual(other.Values);
    }
}
83
Marc Gravell

FWIW、値の内容をハッシュコードで使用することは非常に危険です。変更しないことを保証できる場合にのみ、これを行う必要があります。しかし、公開されているので、保証できるとは思いません。オブジェクトのハッシュコードは変更しないでください。そうでない場合、ハッシュテーブルまたはディクショナリのキーとしての値を失います。 Hashtableでオブジェクトをキーとして使用するという見つけにくいバグを考えてみましょう。外部の影響によりそのハッシュコードが変化し、Hashtableで見つけることができなくなります!

30
Dustin Campbell

HashCodeはオブジェクトを格納するためのキー(ハッシュテーブルに似ています)なので、Id.GetHashCode()だけを使用します

次のようなものはどうですか:

    public override int GetHashCode()
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }

これは、配列で参照比較を行うのではなく、SequenceEqualと互換性がある必要があります。

2
Marc Gravell
public override int GetHashCode() {
   return Id.GetHashCode() ^ Values.GetHashCode();  
}

コメントやその他の回答にはいくつかの良い点があります。 OPは、オブジェクトがディクショナリのキーとして使用された場合、値が「キー」の一部として使用されるかどうかを検討する必要があります。その場合、それらはハッシュコードの一部である必要があります。

一方、GetHashCodeメソッドがSequenceEqualをミラーリングする理由がわかりません。ハッシュテーブルへのインデックスを計算するためのものであり、完全性の完全な決定要因ではありません。上記のアルゴリズムを使用してハッシュテーブルの衝突が多数発生し、それらの値のシーケンスが異なる場合、シーケンスを考慮したアルゴリズムを選択する必要があります。順序が本当に重要でない場合は、時間を節約し、考慮に入れないでください。

1
John Saunders

明らかな(実装が最も簡単な)ソリューションの1つが言及されていないため、別の答えを追加する必要がありました。GetHashCode計算にコレクションを含めないでください。

ここで忘れられたように思われた主なことは、GetHashCodeの結果からの一意性は必要ない(または多くの場合可能性さえない)ことです。等しくないオブジェクトは、等しくないハッシュコードを返す必要はありません。唯一の要件は、等しいオブジェクトが等しいハッシュコードを返すことです。その定義により、次のGetHashCodeの実装はすべてのオブジェクトに対して正しいです(正しいEquals実装があると仮定します):

public override int GetHashCode() 
{ 
    return 42; 
} 

もちろん、これはハッシュテーブル検索で可能な限り最悪のパフォーマンスをもたらします。O(n) O(1)の代わりになりますが、それでも機能的には正しいです。

そのことを念頭に置いて、1つ以上のメンバーとして何らかの種類のコレクションを持つオブジェクトにGetHashCodeを実装するときの一般的な推奨事項は、単にそれらを無視し、にのみ基づいてGetHashCodeを計算することです他のスカラーメンバー。これは、すべてのスカラーメンバーが同じ値を持つ膨大な数のオブジェクトをハッシュテーブルに入れ、結果として同じハッシュコードになる場合を除いて、かなりうまく機能します。

ハッシュコードの計算時にコレクションメンバーを無視すると、ハッシュコード値の分布が減少するにもかかわらず、パフォーマンスが向上する可能性があります。ハッシュコードを使用すると、EqualsをN回呼び出す必要がなくなるため、ハッシュテーブルのパフォーマンスが向上し、代わりにGetHashCodeを1回呼び出すだけで、クイックハッシュテーブルルックアップが必要になります。各オブジェクトに、すべてがハッシュコードの計算に関与する10,000個のアイテムを持つ内部配列がある場合、適切な分散によって得られる利点はおそらく失われます。 生成のコストが大幅に低い場合は、ハッシュコードの分布をわずかに抑えた方がよいでしょう。

1
Allon Guralnek

私はこのようにします:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;
0
Grzenio

IdとValuesが変更されることはなく、Valuesがnullではないという条件で...

public override int GetHashCode()
{
  return Id ^ Values.GetHashCode();
}

クラスは不変ではないことに注意してください。これは配列であるため、だれでも値の内容を変更できるためです。そのため、コンテンツを使用してハッシュコードを生成しようとはしません。

0
Dustin Campbell

私はこのスレッドがかなり古いことを知っていますが、複数のオブジェクトのハッシュコードを計算できるようにするためにこのメソッドを書きました。これはまさにこのケースに非常に役立ちました。それは完璧ではありませんが、私のニーズ、そしておそらくあなたのニーズも満たしています。

私は本当にそれを信用することはできません。いくつかの.net gethashcode実装からコンセプトを得ました。私は419を使用しています(結局、それは私のお気に入りの大きな素数です)が、合理的な素数を選ぶことができます(小さすぎず、大きすぎない)。

だから、ハッシュコードを取得する方法は次のとおりです:

using System.Collections.Generic;
using System.Linq;

public static class HashCodeCalculator
{
    public static int CalculateHashCode(params object[] args)
    {
        return args.CalculateHashCode();
    }

    public static int CalculateHashCode(this IEnumerable<object> args)
    {
        if (args == null)
            return new object().GetHashCode();

        unchecked
        {
            return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode());
        }
    }
}
0
D. Patrick