61

次の 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?
}
4

9 に答える 9

93

このスレッドで発生した問題のために、間違った場合に何が起こるかを示す別の返信を投稿しています...主に、配列の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);
    }
}
于 2009-03-12T15:16:08.393 に答える
39

FWIW、ハッシュ コードで値の内容を使用するのは非常に危険です。絶対に変更されないことが保証できる場合にのみ、これを行う必要があります。ただ、露出なので保証は出来ないと思います。オブジェクトのハッシュコードは決して変更されるべきではありません。そうしないと、Hashtable または Dictionary のキーとしての値が失われます。オブジェクトを Hashtable のキーとして使用するという見つけにくいバグを考えてみてください。外部の影響によりそのハッシュコードが変更され、Hashtable でそれを見つけることができなくなります!

于 2009-03-12T14:17:16.787 に答える
4

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

于 2009-03-12T14:19:25.003 に答える
2

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

    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これは、配列で参照比較を行うのではなく、と互換性があるはずです。

于 2009-03-12T14:14:13.847 に答える
1

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

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

public override int GetHashCode() 
{ 
    return 42; 
} 

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

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

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

于 2012-08-21T16:26:14.967 に答える
1
public override int GetHashCode() {
   return Id.GetHashCode() ^ Values.GetHashCode();  
}

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

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

于 2009-03-12T14:12:18.437 に答える
0

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

私はそれを本当に信用することはできません。いくつかの.netgethashcode実装からコンセプトを取得しました。私は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());
        }
    }
}
于 2010-12-18T19:19:16.447 に答える
0

私はこのようにします:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;
于 2009-03-12T14:13:23.037 に答える
0

Id と Values が変更されず、Values が null でない場合...

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

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

于 2009-03-12T14:14:00.057 に答える