不変の汎用ペア構造体に IEqualityComparer を実装するにはどうすればよいですか?
質問
現在、私はこれを持っています(アドバイスを読んだ後に編集しました):
struct Pair<T, K> : IEqualityComparer<Pair<T, K>>
{
readonly private T _first;
readonly private K _second;
public Pair(T first, K second)
{
_first = first;
_second = second;
}
public T First { get { return _first; } }
public K Second { get { return _second; } }
#region IEqualityComparer<Pair<T,K>> Members
public bool Equals(Pair<T, K> x, Pair<T, K> y)
{
return x.GetHashCode(x) == y.GetHashCode(y);
}
public int GetHashCode(Pair<T, K> obj)
{
int hashCode = obj.First == null ? 0 : obj._first.GetHashCode();
hashCode ^= obj.Second == null ? 0 : obj._second.GetHashCode();
return hashCode;
}
#endregion
public override int GetHashCode()
{
return this.GetHashCode(this);
}
public override bool Equals(object obj)
{
return (obj != null) &&
(obj is Pair<T, K>) &&
this.Equals(this, (Pair<T, K>) obj);
}
}
問題は、First と Second が参照型ではない可能性があることです (実際、VS はこれについて警告します)。それでもコードはコンパイルされます。比較する前に、それら (First と Second) をオブジェクトにキャストする必要がありますか、それともこれを行うより良い方法はありますか?
編集:注意してください、私は 欲しい この構造体は値と参照型をサポートします (つまり、クラスによる制約は有効な解決策ではありません)。
編集2:私が何を達成しようとしているのかというと、これを辞書で機能させたいと思っています。第二に、SRP は今のところ私にとって重要ではありません。それは実際にはこの問題の本質ではないためです。SRP は後でいつでもリファクタリングできます。第三に、default(T) との比較は、null との比較の代わりに機能しません。試してみてください。
解決
代わりに IEquatable が必要なようです。
internal struct Pair<T, K> : IEquatable<Pair<T, K>>
{
private readonly T _first;
private readonly K _second;
public Pair(T first, K second)
{
_first = first;
_second = second;
}
public T First
{
get { return _first; }
}
public K Second
{
get { return _second; }
}
public bool Equals(Pair<T, K> obj)
{
return Equals(obj._first, _first) && Equals(obj._second, _second);
}
public override bool Equals(object obj)
{
return obj is Pair<T, K> && Equals((Pair<T, K>) obj);
}
public override int GetHashCode()
{
unchecked
{
return (_first != null ? _first.GetHashCode() * 397 : 0) ^ (_second != null ? _second.GetHashCode() : 0);
}
}
}
他のヒント
IEqualityComparer 実装は別のクラスである必要があります (参照を再利用したいので、構造体ではないことは間違いありません)。
また、構造体のデフォルトの GetHashcode 実装 (オーバーライドしない) ではそのメンバーが考慮されるため、ハッシュコードは決してキャッシュすべきではありません。
メソッドの比較にハッシュコードを使用する場合は、ハッシュ コードが同じかどうかを「実際の値」かどうかチェックする必要があります。
bool result = ( x._hashCode == y._hashCode );
if ( result ) { result = ( x._first == y._first && x._second == y._second ); }
// OR?: if ( result ) { result = object.Equals( x._first, y._first ) && object.Equals( x._second, y._second ); }
// OR?: if ( result ) { result = object.ReferenceEquals( x._first, y._first ) && object.Equals( x._second, y._second ); }
return result;
ただし、「_first」フィールドと「_second」フィールドの比較には少し問題があります。デフォルトでは、参照型は前等価比較「object.ReferenceEquals」メソッドを使用しますが、それらをオーバーライドできます。したがって、正しい解決策は、比較方法が「正確に何をすべきか」によって異なります。「_first」および「_second」フィールドの「Equals」メソッド、または object.ReferenceEquals を使用する必要がありますか?それとももっと複雑なものでしょうか?
警告に関しては、null の代わりに、default(T) およびdefault(K) を使用できます。
あなたが何を達成しようとしているのかわかりませんが、等しいかどうかを比較するためにハッシュコードを使用すべきではありません。2 つの異なるオブジェクトが同じハッシュコードを持たないという保証はありません。また、構造体は不変であっても、メンバー _first と _second は不変ではありません。
まず第一に、このコードは SRP 原則に違反しています。ペア クラスはアイテムのペアを保持していましたよね。等価比較機能をそれに委任するのは正しくありません。
次にコードを見てみましょう。
引数の 1 つが null の場合、Equals メソッドは失敗します。ダメです。Equals は、Pair クラスのハッシュ コードを使用しますが、GetHashCode の定義を見てください。これはペア メンバーのハッシュ コードを組み合わせただけであり、項目の等価性とは何の関係もありません。Equals メソッドが実際のデータを比較することを期待します。残念ながら、現時点では多忙のため、正しい実装を提供することができません。しかし、一見したところ、コードは間違っているように見えます。達成したいことについて説明していただければ幸いです。SOメンバーがきっとアドバイスをくれると思います。
ラムダ式をパラメータとして使用することを提案できますか?これにより、内部ジェネリック型を比較する方法を指定できるようになります。
コンパイル時にこれに関する警告は表示されませんが、 == null 比較について話していると思いますか?キャストすると、これがいくらかすっきりするように思えます。
PS.比較子には別のクラスを使用する必要があります。2 つの役割 (ペアであることとペアの比較) を満たすこのクラスは、明らかに醜いです。