もう一度平等について話し合っていて、偶然見つけましたEqualityComparer<T>.Default.Equals()
。私はこのメソッドをobject.Equals()
.
今、私はひどく間違っていたと思います。
object.Equals()
実装されている場合は呼び出しますがEquals()
、正しいポリモーフィック動作を提供するオーバーライド可能なインスタンス メソッドを使用します。EqualityComparer<T>.Default.Equals()
IEquatable<T>.Equals()
この小さなプログラムを考えてみましょう:
public class Class1 : IEquatable<Class1>
{
public int Prop1 { get; set; }
public bool Equals(Class1 other)
{
if (other == null)
return false;
return Prop1 == other.Prop1;
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return Equals(obj as Class1);
}
}
public class Class2 : Class1, IEquatable<Class2>
{
public int Prop1 { get; set; }
public int Prop2 { get; set; }
public bool Equals(Class2 other)
{
if (other == null)
return false;
return Prop1 == other.Prop1 && Prop2 == other.Prop2;
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return Equals(obj as Class2);
}
}
class Program
{
static void Main(string[] args)
{
var c1 = new Class1 {Prop1 = 10};
var c2 = new Class2 {Prop1 = 10, Prop2 = 5};
var c3 = new Class2 {Prop1 = 10, Prop2 = 15};
Console.WriteLine("Object.Equals()");
Console.WriteLine("C1=C2 {0}",Equals(c1,c2));
Console.WriteLine("C2=C1 {0}",Equals(c2, c1));
Console.WriteLine("C2=C3 {0}",Equals(c2, c3));
Console.WriteLine("C3=C2 {0}", Equals(c3, c2));
var dec1 = EqualityComparer<Class1>.Default;
Console.WriteLine();
Console.WriteLine("EqualityComparer<Class1>.Default.Equals");
Console.WriteLine("C1=C2 {0}", dec1.Equals(c1, c2));
Console.WriteLine("C2=C1 {0}", dec1.Equals(c2, c1));
Console.WriteLine("C2=C3 {0} BUG?", dec1.Equals(c2, c3));
Console.WriteLine("C3=C2 {0} BUG?", dec1.Equals(c3, c2));
Console.ReadKey();
}
}
これは、平等のセマンティクスに矛盾をもたらしやすいことを示しています。
Object.Equals()
C1=C2 False
C2=C1 False
C2=C3 False
C3=C2 False
EqualityComparer<Class1>.Default.Equals
C1=C2 False
C2=C1 False
C2=C3 True バグ?
C3=C2 本当のバグ?
ただし、 MSDN ドキュメントでは次のことを推奨しています。
実装者への注意 Equals を実装する場合は、Object.Equals(Object) および GetHashCode の基本クラスの実装もオーバーライドして、それらの動作が IEquatable<T>.Equals メソッドの動作と一致するようにする必要があります。Object.Equals(Object) をオーバーライドする場合、オーバーライドされた実装は、クラスの静的 Equals(System.Object, System.Object) メソッドの呼び出しでも呼び出されます。さらに、op_Equality および op_Inequality 演算子をオーバーロードする必要があります。これにより、等しいかどうかのすべてのテストで一貫した結果が返されることが保証されます。これは例が示しています。
この瞬間からIEquatable<T>
、参照型を実装する理由がわかりません。意味があるときは誰か教えてもらえますか?型を (基本型として) 別の方法で見た場合、異なる等価動作を一貫性のないものとして扱うべきでしょうか?