どうにもわからないEqualsやらGetHashCodeやらその辺のお話。
R#がリファレンスだろjkとかIEquatable<T>は値型用だから参照型への実装は云々など枝葉の話はよく聞くんですがどうにも実装しててややこしいので整理の意味でエントリ。
特にポリモーフィズムな型の等値性判断のあたりを何とかしたいなと。。。
関係ありそうなところをまず張っときます。
まずは基本のobject.Equals(object obj)
public class Test
{
public string Name { get; set; }
public string Address { get; set; }
}
みたいな型があったとしたら実装は
public override bool Equals(System.Object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to Point return false.
Test p = obj as Test;
if ((System.Object)p == null)
{
return false;
}
// Return true if the fields match:
return (Name == p.Name) && (Address == p.Address);
}
忘れずにGetHashCodeも実装
public override int GetHashCode()
{
var hash = Name.GetHashCode();
hash += 1234 ^ Address.GetHashCode();
return hash;
}
この辺はまぁ割と普通の実装なのでそうはずしてはないかと。
で、値型でのBoxing回避やらDictionaryとかの流れでIEquatable<T>、おまけで==と!=あたりを実装してくかと思います。
この辺で実装が色々バリエーションが出てくるんですがベーシックに参照比較を織り交ぜつつこんな感じで。
public bool Equals(Test other)
{
if (Object.ReferenceEquals(other, null))
{
return false;
}
if (Object.ReferenceEquals(this, other))
{
return true;
}
if (this.GetType() != other.GetType())
return false;
return (Name == other.Name) && (Address == other.Address);
}
で、もともとのObjrct.Equalsは型安全なほうに委譲。
public override bool Equals(object obj)
{
return this.Equals(obj as Test);
}
ここからが割と本題で派生クラスで実装する場合。まぁほとんど同じですがメンバの比較やGetHashCodeでbase.Equalsみたいな感じで追加したメンバだけを入れ込んでいくのがベーシックなやり方だと思います。
public class TestEx : Test, IEquatable<TestEx>
{
public string PhoneNumber { get; set; }
public override bool Equals(object obj)
{
return base.Equals((TestEx)obj);
}
public override int GetHashCode()
{
var hash = base.GetHashCode();
hash += 1234 ^ PhoneNumber.GetHashCode();
return hash;
}
public bool Equals(TestEx other)
{
return base.Equals(other) && PhoneNumber == other.PhoneNumber;
}
}
この辺からちょっぴり自信がないですがまぁだいたいこんな感じかと。
で、こういったケースだとそれぞれの型の比較はいいんですが基底クラスで比較した場合(今回だとTest)にTestのメンバのみで比較されちゃうのでうまくいきません。ポリモーフィズムな作りなのでoverrideされたものがうまく使われてほしいところですが呼び出し時にObjectにするなりしないと結局うまくいかないです。こういうものだって言われればそうなんですが==とかオーバロードした時には派生型で見てほしいのが人情かと思います。
もちろん基底側でObject.Equalsを軸にすれば期待する結果になるんですがタイプセーフじゃなくなる(ってほどでもないかも)のはどうにも違う感じなので。
派生クラスでは基底クラスのIEquatable<T>を実装するのが実はルールだったりするのかしらん?だとしたらヘビーすぎるのでIEquatable<T>いらにゃい!と言いたいです。はい。
おそらく根本的な理解がないのでMSDNやらのバラバラな実装に振り回されてるんだとは思いますが(EqualsやIEquatableの)おおもとの設計意図や最適解を導ければなぁと思いエントリしました。みなさんこんなケースではどう実装してますか?