値オブジェクトの IEqualityComparer
-
21-09-2019 - |
質問
不変の値オブジェクト IPathwayModule があり、その値は次のように定義されます。
- (int) ブロック;
- (エンティティ) モジュール。(文字列) ModuleId によって識別されます。
- (列挙型) ステータス。そして
- (エンティティ) クラス。(文字列) ClassId によって識別されます。これは null の場合があります。
これが私の現在の IEqualityComparer 実装です。いくつかの単体テストで動作するようです。しかし、自分がやっていることが正しいかどうかを知るほどには理解していないと思います。以前の実装では、テストを繰り返し実行すると失敗することがありました。
private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
public bool Equals(IPathwayModule x, IPathwayModule y)
{
int hx = GetHashCode(x);
int hy = GetHashCode(y);
return hx == hy;
}
public int GetHashCode(IPathwayModule obj)
{
int h;
if (obj.Class != null)
{
h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + obj.Class.ClassId.GetHashCode();
}
else
{
h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + "NOCLASS".GetHashCode();
}
return h;
}
}
IPathwayModule は間違いなく不変であり、HashSet 内のアイテムとして使用されるため、同じ値を持つ異なるインスタンスは等しく、同じ HashCode を生成する必要があります。
私の質問は次のとおりだと思います。
- この場合、インターフェイスを正しく使用していますか?
- 望ましい動作が得られない場合がありますか?
- 堅牢性やパフォーマンスを向上させる方法はありますか?
- 私が守っていない良い習慣はありますか?
解決 7
答えたすべての人に感謝します。私は答えた皆からのフィードバックを集約しているし、私の改善IEqualityComparer
は今のように見えます:
private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
public bool Equals(IPathwayModule x, IPathwayModule y)
{
if (x == y) return true;
if (x == null || y == null) return false;
if ((x.Class == null) ^ (y.Class == null)) return false;
if (x.Class == null) //and implicitly y.Class == null
{
return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId);
}
return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId) && x.Class.ClassId.Equals(y.Class.ClassId);
}
public int GetHashCode(IPathwayModule obj)
{
unchecked {
int h = obj.Block ^ obj.Module.ModuleId.GetHashCode() ^ (int) obj.Status;
if (obj.Class != null)
{
h ^= obj.Class.ClassId.GetHashCode();
}
return h;
}
}
}
他のヒント
Doが、それはあまりにも壊れやすいのハッシュ関数の結果の面内のequalsをしません。むしろ各フィールドのフィールド値の比較を行います。ような何かます:
return x != null && y != null && x.Name.Equals(y.Name) && x.Type.Equals(y.Type) ...
また、ハッシュ関数の結果は、加算には本当に適していません。代わりに^
演算子を使用してみてください。
return obj.Name.GetHashCode() ^ obj.Type.GetHashCode() ...
あなたはGetHashCodeメソッドでnullのチェックは必要ありません。その値がnullの場合、あなたは大きな問題を持っている、あなたが制御することはできませんその上に何かから回復しようとしていない使用...
唯一の大きな問題は、対等の実装です。ハッシュコードを使用すると、異なるオブジェクトに対して同じハッシュコードを取得することができ、一意ではありません。あなたは個別にIPathwayModuleの各フィールドを比較する必要があります。
GetHashCodeメソッド()ビットを向上させることができます。あなたはint型に()GetHashCodeメソッドを呼び出す必要はありません。 int型自体は良いハッシュコードです。列挙値に同じ。あなたのGetHashCodeメソッドは、このように実装することができます:
public int GetHashCode(IPathwayModule obj)
{
unchecked {
int h = obj.Block + obj.Module.ModeleId.GetHashCode() + (int) obj.Status;
if (obj.class != null)
h += obj.Class.ClassId.GetHashCode();
return h;
}
}
算術演算でありオーバーフローすることができるので、「未チェック」のブロックが必要である。
あなたは、比較対象の主な方法として、GetHashCodeメソッド()を使用しないでください。フィールド単位のそれを比較してください。
(これは、「ハッシュコードの衝突」と呼ばれる)同じハッシュコードを持つ複数のオブジェクトがある可能性があります。
また、あなたが簡単にOverflowExceptionがを引き起こす可能性があるため、一緒に複数の整数値を追加するときに注意してください。使用「排他的論理和」(^)「未チェック」のブロックにハッシュコードまたはラップコードを組み合わせることがます。
あなたが等しく、GetHashCodeメソッドのより良いバージョンを実装する必要があります。
は例えば、列挙型のハッシュコードは、単にそれらの数値である。
これら二つの列挙型と言い換える、
public enum A { x, y, z }
public enum B { k, l, m }
次に、あなたの実装で、次の値の種類:
public struct AB {
public A;
public B;
}
次の2つの値が等しいと見なされるであろう:
AB ab1 = new AB { A = A.x, B = B.m };
AB ab2 = new AB { A = A.z, B = B.k };
私はあなたがそれを望んでいないと仮定しています。
また、インタフェースとして値型を渡すことは、おそらくあまりしていないが、これは、パフォーマンス上の問題を持つことができ、それらをボックスします。あなたがたIEqualityComparerの実装が直接あなたの値の型を取ることを検討することがあります。
- ハッシュ コードが等しいため 2 つのオブジェクトが等しいと仮定するのは間違いです。すべてのメンバーを個別に比較する必要があります
- ハッシュ コードを結合するには + ではなく ^ を使用する方がよいでしょう。
私があなたのことをよく理解しているのであれば、あなたのコードについていくつかコメントを聞きたいと思います。私のコメントは次のとおりです。
GetHashCode
加算するのではなく、XOR 演算する必要があります。XOR (^
) 衝突を防ぐ可能性が高くなります- ハッシュコードを比較します。それは良いことですが、これを行うのは、基になるオブジェクトが
GetHashCode
. 。そうでない場合は、プロパティとそのハッシュコードを使用し、それらを組み合わせます。 - ハッシュ コードは重要であり、これにより迅速な比較が可能になります。ただし、ハッシュ コードが等しい場合でも、オブジェクトは異なる可能性があります。これはまれに起こります。ただし、ハッシュ コードが等しい場合は、オブジェクトのフィールドを比較する必要があります。
- 値の型は不変だと言いますが、オブジェクトを参照しています(
.Class
)、不変ではありません - 最初のテストとして参照比較を追加することで、常に比較を最適化します。参照が等しくなく、オブジェクトも等しくなく、構造体も等しくありません。
ポイント 5 は、値の型で参照するオブジェクトが同じ参照でない場合に等しくない値を返すかどうかによって異なります。
編集: 多くの文字列を比較します。文字列比較は C# で最適化されます。他の人が提案したように、より適切に使用できます ==
それらと比較してください。GetHashCode の場合は、OR を使用します。 ^
他の人からも提案されているように。