次の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?
}
このスレッドで発生した問題のため、間違った場合に何が起こるかを示す別の返信を投稿しています...主に、配列の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);
}
}
FWIW、値の内容をハッシュコードで使用することは非常に危険です。変更しないことを保証できる場合にのみ、これを行う必要があります。しかし、公開されているので、保証できるとは思いません。オブジェクトのハッシュコードは変更しないでください。そうでない場合、ハッシュテーブルまたはディクショナリのキーとしての値を失います。 Hashtableでオブジェクトをキーとして使用するという見つけにくいバグを考えてみましょう。外部の影響によりそのハッシュコードが変化し、Hashtableで見つけることができなくなります!
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
と互換性がある必要があります。
public override int GetHashCode() {
return Id.GetHashCode() ^ Values.GetHashCode();
}
コメントやその他の回答にはいくつかの良い点があります。 OPは、オブジェクトがディクショナリのキーとして使用された場合、値が「キー」の一部として使用されるかどうかを検討する必要があります。その場合、それらはハッシュコードの一部である必要があります。
一方、GetHashCodeメソッドがSequenceEqualをミラーリングする理由がわかりません。ハッシュテーブルへのインデックスを計算するためのものであり、完全性の完全な決定要因ではありません。上記のアルゴリズムを使用してハッシュテーブルの衝突が多数発生し、それらの値のシーケンスが異なる場合、シーケンスを考慮したアルゴリズムを選択する必要があります。順序が本当に重要でない場合は、時間を節約し、考慮に入れないでください。
明らかな(実装が最も簡単な)ソリューションの1つが言及されていないため、別の答えを追加する必要がありました。GetHashCode
計算にコレクションを含めないでください。
ここで忘れられたように思われた主なことは、GetHashCode
の結果からの一意性は必要ない(または多くの場合可能性さえない)ことです。等しくないオブジェクトは、等しくないハッシュコードを返す必要はありません。唯一の要件は、等しいオブジェクトが等しいハッシュコードを返すことです。その定義により、次のGetHashCode
の実装はすべてのオブジェクトに対して正しいです(正しいEquals
実装があると仮定します):
public override int GetHashCode()
{
return 42;
}
もちろん、これはハッシュテーブル検索で可能な限り最悪のパフォーマンスをもたらします。O(n) O(1)の代わりになりますが、それでも機能的には正しいです。
そのことを念頭に置いて、1つ以上のメンバーとして何らかの種類のコレクションを持つオブジェクトにGetHashCode
を実装するときの一般的な推奨事項は、単にそれらを無視し、にのみ基づいてGetHashCode
を計算することです他のスカラーメンバー。これは、すべてのスカラーメンバーが同じ値を持つ膨大な数のオブジェクトをハッシュテーブルに入れ、結果として同じハッシュコードになる場合を除いて、かなりうまく機能します。
ハッシュコードの計算時にコレクションメンバーを無視すると、ハッシュコード値の分布が減少するにもかかわらず、パフォーマンスが向上する可能性があります。ハッシュコードを使用すると、Equals
をN回呼び出す必要がなくなるため、ハッシュテーブルのパフォーマンスが向上し、代わりにGetHashCodeを1回呼び出すだけで、クイックハッシュテーブルルックアップが必要になります。各オブジェクトに、すべてがハッシュコードの計算に関与する10,000個のアイテムを持つ内部配列がある場合、適切な分散によって得られる利点はおそらく失われます。 生成のコストが大幅に低い場合は、ハッシュコードの分布をわずかに抑えた方がよいでしょう。
私はこのようにします:
long result = Id.GetHashCode();
foreach(T val in Values)
result ^= val.GetHashCode();
return result;
IdとValuesが変更されることはなく、Valuesがnullではないという条件で...
public override int GetHashCode()
{
return Id ^ Values.GetHashCode();
}
クラスは不変ではないことに注意してください。これは配列であるため、だれでも値の内容を変更できるためです。そのため、コンテンツを使用してハッシュコードを生成しようとはしません。
私はこのスレッドがかなり古いことを知っていますが、複数のオブジェクトのハッシュコードを計算できるようにするためにこのメソッドを書きました。これはまさにこのケースに非常に役立ちました。それは完璧ではありませんが、私のニーズ、そしておそらくあなたのニーズも満たしています。
私は本当にそれを信用することはできません。いくつかの.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());
}
}
}