Equals
いつもの id:atsushieno:20050218 より、
>> id:atsushieno:20050218
何でa.Equals(b)をb.Equals(a)にしただけで問題が消えて無くなったんでしょうねぇ。
元ネタは分からないが、Equals の交換法則はよくある問題な気がする。
Object.Equals() の説明では
>> Object.Equals Method
次に示すステートメントは、 Equals メソッドのすべての実装に対し true である必要があります。リストの x、y、および z は、 null 参照ではないオブジェクト参照を表します。
- x.Equals(x) は、浮動小数点型が関連する場合を除き、 true を返します。
- x.Equals(y) は y.Equals(x) と同じ値を戻します。
- x.Equals(y) は、x と y が両方とも NaN である場合に true を返します。
- (x.Equals(y) && y.Equals(z)) は、x.Equals(z) が true を返す場合に限り、 true を返します。
- x.Equals(y) を連続して呼び出す場合、x と y が参照するオブジェクトが変更されていない限り、同じ値が返されます。
- x.Equals(null) は false を返します。
Equals メソッドに関するその他の必須動作については、 GetHashCode メソッドのトピックを参照してください
となっていて、a, b が null でない限り a.Equals(b) と b.Equals(a) は同じ値を戻さなければならない。
これは、一見当然のことにみえるが、実際は次のような実装が多くみうけられる。
public class A
{
private int number;
public virtual int Number
{
get { return this.number; }
set { this.number = value; }
}
public override bool Equals(object obj)
{
A that = obj as A;
return (that != null)
&& (that.Number == this.Number);
}
public override int GetHashCode()
{
return this.Number;
}
}このクラスの実装は、この時点では前述の条件すべてを満たしている。
ここで、次のような実装を追加する。
public class B : A
{
private int subNumber;
public virtual int SubNumber
{
get { return this.subNumber; }
set { this.subNumber = value; }
}
public override bool Equals(object obj)
{
if (base.Equals(obj))
{
B that = obj as B;
return (that != null)
&& (that.SubNumber == this.SubNumber);
}
return false;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}この時点で、A のインスタンス a と、B のインスタンス b において、a.Equals(b) が true であっても、b.Equals(a) が false であるという状況が生まれてしまう。
さて、A が既存の実装の場合、B の実装者はどのような Equals を実装すればよいだろうか?
Java では、
google:JavaHouse "How to override equals(Object)"
さわりしか読んでません*1が、非常に長い話になってますが、同じ話題だと思います。
*1:スレッド切れが追いにくい