ハッシュコードは、使用されている等価性の定義に依存する必要がありますA == B
(A.GetHashCode() == B.GetHashCode()
ただし、必ずしも逆である必要はありA.GetHashCode() == B.GetHashCode()
ませんA == B
。
デフォルトでは、値型の等価定義はその値に基づいており、参照型の等価定義はその ID に基づいています (つまり、デフォルトでは、参照型のインスタンスはそれ自体とのみ等しい)。したがって、デフォルトのハッシュコードは値の型は、含まれるフィールドの値に依存し*、参照型の場合はアイデンティティに依存します。実際、等しくないオブジェクトのハッシュコードは、特に下位ビット (再ハッシュの値に影響を与える可能性が最も高い) で異なることが理想的であるため、通常、2 つの同等であるが等しくないオブジェクトには異なる値が必要です。ハッシュ。
オブジェクトはそれ自体と等しいままであるため、オブジェクトが変更された場合でも、このデフォルトの実装がGetHashCode()
同じ値を持ち続けることも明確にする必要があります (変更可能なオブジェクトのアイデンティティーは変更されません)。
場合によっては、参照型 (または値型) が等価性を再定義します。この例は文字列です。たとえば、"ABC" == "AB" + "C"
. 比較される文字列の 2 つの異なるインスタンスがありますが、それらは等しいと見なされます。この場合GetHashCode()
、値が等価性が定義されている状態 (この場合は含まれる文字のシーケンス) に関連するようにオーバーライドする必要があります。
これは、不変でもある型で行うのがより一般的ですが、さまざまな理由から、GetHashCode()
immutability に依存しません。むしろ、GetHashCode()
可変性に直面しても一貫性を保つ必要があります - ハッシュの決定に使用する値を変更すると、ハッシュはそれに応じて変更します。ただし、この変更可能なオブジェクトをハッシュを使用して構造体のキーとして使用している場合、これは問題であることに注意してください。オブジェクトを変更すると、その位置に移動せずに、格納する位置が変更されるためです (コレクション内のオブジェクトの位置がその値に依存するその他のケース - たとえば、リストをソートしてからリスト内の項目の 1 つを変更すると、リストはソートされなくなります)。ただし、これは、辞書とハッシュセットで不変オブジェクトのみを使用する必要があるという意味ではありません。むしろ、そのような構造にあるオブジェクトを変更してはならないことを意味し、不変にすることはこれを保証する明確な方法です。
実際、可変オブジェクトをそのような構造に格納することが望ましいケースはかなり多く、この間にそれらを変更しない限り、これは問題ありません。不変性がもたらす保証がないため、別の方法でそれを提供したいと考えています (たとえば、コレクション内で短時間過ごし、1 つのスレッドからのみアクセスできるようにするなど)。
したがって、キー値の不変性は、何かが可能な場合の 1 つですが、一般的にはアイデアです。ただし、ハッシュコードアルゴリズムを定義する人にとって、そのようなケースが常に悪い考えであると想定することはできません(オブジェクトがそのような構造に格納されている間に突然変異が発生したことさえ知りません)。オブジェクトの現在の状態で定義されたハッシュコードを実装するのは、特定の時点でそれを呼び出すことが良いかどうかに関係なく、彼らのためです。したがって、たとえば、変更のたびにメモ化がクリアされない限り、変更可能なオブジェクトでハッシュコードをメモ化するべきではありません。(同じオブジェクトのハッシュコードに繰り返しヒットする構造は、独自のメモ化を行うため、一般的に、ハッシュをメモ化するのは無駄です)。
さて、手元のケースでは、ArrayList は、同一性に基づくデフォルトの等価ケースで動作します。たとえば、次のようになります。
ArrayList a = new ArrayList();
ArrayList b = new ArrayList();
for(int i = 0; i != 10; ++i)
{
a.Add(i);
b.Add(i);
}
return a == b;//returns false
さて、これは実際には良いことです。なんで?上記で、a と b が等しいと見なしたいことをどのように知ることができますか? 可能ですが、他のケースでもそうしない十分な理由があります。
さらに、平等を価値ベースからアイデンティティベースに再定義するよりも、アイデンティティベースから価値ベースに再定義する方がはるかに簡単です。最後に、多くのオブジェクトに対して複数の値ベースの等価定義が存在するため (古典的なケースでは、文字列を等価にするものについてさまざまな見解が示されます)、機能する唯一無二の定義さえありません。例えば:
ArrayList c = new ArrayList();
for(short i = 0; i != 10; ++i)
{
c.Add(i);
}
a == b
上記で検討した場合、同様に検討する必要がa == c
ありますか?答えは、使用している平等の定義で何を気にするかに依存するため、すべてのケースが一致しないため、フレームワークはすべてのケースの正しい答えを知ることができません。
さて、特定のケースで値ベースの平等に関心がある場合、2 つの非常に簡単なオプションがあります。1 つ目は、同等性をサブクラス化してオーバーライドすることです。
public class ValueEqualList : ArrayList, IEquatable<ValueEqualList>
{
/*.. most methods left out ..*/
public Equals(ValueEqualList other)//optional but a good idea almost always when we redefine equality
{
if(other == null)
return false;
if(ReferenceEquals(this, other))//identity still entails equality, so this is a good shortcut
return true;
if(Count != other.Count)
return false;
for(int i = 0; i != Count; ++i)
if(this[i] != other[i])
return false;
return true;
}
public override bool Equals(object other)
{
return Equals(other as ValueEqualList);
}
public override int GetHashCode()
{
int res = 0x2D2816FE;
foreach(var item in this)
{
res = res * 31 + (item == null ? 0 : item.GetHashCode());
}
return res;
}
}
これは、そのようなリストを常にこのように扱いたいと考えていることを前提としています。特定のケースに対して IEqualityComparer を実装することもできます。
public class ArrayListEqComp : IEqualityComparer<ArrayList>
{//we might also implement the non-generic IEqualityComparer, omitted for brevity
public bool Equals(ArrayList x, ArrayList y)
{
if(ReferenceEquals(x, y))
return true;
if(x == null || y == null || x.Count != y.Count)
return false;
for(int i = 0; i != x.Count; ++i)
if(x[i] != y[i])
return false;
return true;
}
public int GetHashCode(ArrayList obj)
{
int res = 0x2D2816FE;
foreach(var item in obj)
{
res = res * 31 + (item == null ? 0 : item.GetHashCode());
}
return res;
}
}
要約すれば:
- 参照型の既定の等価定義は、ID のみに依存します。
- ほとんどの場合、私たちはそれを望んでいます。
- クラスを定義する人が、これが必要なものではないと判断した場合、この動作をオーバーライドできます。
- クラスを使用する人が再び別の等価性の定義を必要
IEqualityComparer<T>
とIEqualityComparer
する場合、その辞書、ハッシュマップ、ハッシュセットなどで等価性の概念を使用することができます。
- ハッシュベースの構造の鍵であるオブジェクトを変更するのは悲惨なことです。不変性を使用して、これが起こらないようにすることができますが、必須ではなく、常に望ましいものでもありません。
全体として、このフレームワークは優れたデフォルトと詳細なオーバーライドの可能性を提供してくれます。
*構造体内の小数の場合にはバグがあります。これは、安全な場合とそうでない場合に、構造体でショートカットが使用される場合があるためです。カットは安全ではないため、安全なケースとして誤って識別されます。