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:スレッド切れが追いにくい