まあ、どの実装にも長所と短所がありGetHashCode()
ます。これらはもちろん、独自のものを実装する際に検討するものですValueType.GetHashCode()
が、具象型の実際の詳細がどうなるかについて多くの情報を持っていないという点で特に問題があります。もちろん、これは、抽象クラスや、状態に関してさらに多くを追加するクラスのベースになることを意図したクラスを作成しているときによく起こりますが、そのような場合、デフォルトの実装を使用するだけの明らかな解決策があります。object.GetHashCode()
派生クラスがそこでオーバーライドすることを気にしない限り。
値型と参照型の主な違いは、ValueType.GetHashCode()
スタックとヒープの実装の詳細について話すのが一般的であるにもかかわらず、値型の等価性は値に関連し、オブジェクト型の等価性は同一性に関連します (オブジェクトがオーバーライドによって異なる形式の等価性を定義し、Equals()
参照GetHashCode()
等価性の概念がまだ存在し、依然として有用である場合でも.
したがって、Equals()
メソッドの実装は明らかです。2 つのオブジェクトが同じ型であることを確認し、同じ型である場合は、すべてのフィールドが等しいことも確認します (実際には、場合によってはビットごとの比較を行う最適化がありますが、それは同じ基本的な考え方に基づいた最適化です)。
何のためにするのかGetHashCode()
? 完全な解決策はありません。彼らができることの 1 つは、すべてのフィールドで、ある種の複数してから追加するか、シフトしてから xor することです。それはおそらくかなり良いハッシュコードを与えるでしょうが、多くのフィールドがある場合は高価になる可能性があります(多くのフィールドを持つ値型を持つことは推奨されていないことを気にしないでください.実装者はそれらがまだ可能であることを考慮する必要があります.それが理にかなっている場合もあるかもしれませんが、正直なところ、それが理にかなっていて、それをハッシュすることも理にかなっているとは想像できません)。一部のフィールドがインスタンス間でめったに変わらないことを知っていれば、それらのフィールドを無視しても、非常に高速でありながら、非常に優れたハッシュコードを保持できます。最後に、彼らはほとんどのフィールドを無視することができ、無視しないフィールドの値がほとんどの場合変化することを期待します。
(インスタンス フィールドがない場合に何が行われるかは別の問題であり、かなり良い選択です。そのような値の型は、同じ型の他のすべてのインスタンスと等しく、それに一致するハッシュコードを持っています)。
したがって、最初のフィールドが同じである(または同じハッシュコードを返す)多くの値をハッシュしている場合、それはうまくいかない実装ですが、他の場合は他の実装がうまくいかないでしょう(Monoはすべてのフィールドのハッシュコードを一緒にxorし、あなたの場合は良く、他の場合は悪い)。
フィールドの順序を変更することは重要ではありません。ハッシュコードは、プロセスの存続期間中のみ有効であり、それを超えて永続化できるほとんどの場合には適していないことが明確に述べられているためです (一部のキャッシング状況で役立ちます)コードの変更後に正しく検出されなくても問題はありません)。
ですから、素晴らしいことではありませんが、完璧なものはありません。オブジェクトをキーとして使用する場合、「平等」が意味することの両面を常に考慮しなければならないことを示しています。あなたのケースでは、次のように簡単に修正できます。
public class KVPCmp<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>, IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
if(x == null)
return y == null;
if(y == null)
return false;
if(!(x is KeyValuePair<TKey, TValue>) || !(y is KeyValuePair<TKey, TValue>))
throw new ArgumentException("Comparison of KeyValuePairs only.");
return Equals((KeyValuePair<TKey, TValue>) x, (KeyValuePair<TKey, TValue>) y);
}
public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
{
return x.Key.Equals(y.Key) && x.Value.Equals(y.Value);
}
public int GetHashCode(KeyValuePair<TKey, TValue> obj)
{
int keyHash = obj.GetHashCode();
return ((keyHash << 16) | (keyHash >> 16)) ^ obj.Value.GetHashCode();
}
public int GetHashCode(object obj)
{
if(obj == null)
return 0;
if(!(obj is KeyValuePair<TKey, TValue>))
throw new ArgumentException();
return GetHashCode((KeyValuePair<TKey, TValue>)obj);
}
}
ディクショナリを作成するときにこれをコンパレータとして使用すると、すべてがうまくいくはずです(実際にはジェネリックコンパレータメソッドのみが必要ですが、残りを残しても害はなく、場合によっては便利です)。