任何人对IEquatable<T>
或IComparable<T>
通常是否应该要求T
为sealed
(如果是class
)有任何意见?
这个问题发生在我身上,因为我正在编写一组旨在帮助实现不可变类的基类。基类要提供的部分功能是自动实现相等比较(使用类的字段以及可应用于字段来控制相等比较的属性)。当我完成它时应该非常好 - 我正在使用表达式树为每个T
动态创建一个编译的比较函数,因此比较函数应该非常接近正则相等比较函数的性能。 (我正在使用在System.Type
上键入的不可变字典,并使用双重检查锁定以合理的方式存储生成的比较函数。
虽然出现了一件事,但是用于检查成员字段相等性的函数。我的初衷是检查每个成员字段的类型(我称之为X
)是否实现IEquatable<X>
。但是,经过一番思考,除非X
为sealed
,否则我认为这样做不安全。原因是,如果X
不是sealed
,我无法确定X
是否正确地将等式检查委托给X
上的虚拟方法,从而允许子类型以覆盖相等比较。
然后提出了一个更普遍的问题 - 如果一个类型没有被密封,它是否应该真正实现这些接口?我想不会,因为我认为接口契约是比较两种X
类型,而不是两种类型,可能是也可能不是X
(尽管它们当然必须是X
}或子类型。
你们觉得怎么样?对于未密封的课程,应该避免IEquatable<T>
和IComparable<T>
吗? (也让我想知道是否有一个fxcop规则)
我目前的想法是让我生成的比较函数仅对IEquatable<T>
为T
的成员字段使用sealed
,而使用虚拟Object.Equals(Object obj)
if {{即使T
实现T
,也会启封1}},因为该字段可能存储IEquatable<T>
的子类型,我怀疑大多数T
的实现都是为继承而设计的。< / p>
答案 0 :(得分:19)
我一直在考虑这个问题,经过一些考虑,我同意只应在密封类型上实现IEquatable<T>
和IComparable<T>
。
我来回走了一会儿然后我想到了下面的测试。在什么情况下,以下应该返回虚假?恕我直言,2个对象要么相等,要么不对。
public void EqualitySanityCheck<T>(T left, T right) where T : IEquatable<T> {
var equals1 = left.Equals(right);
var equals2 = ((IEqutable<T>)left).Equals(right);
Assert.AreEqual(equals1,equals2);
}
假定比较器属于等效类型,IEquatable<T>
对给定对象的结果应与Object.Equals
具有相同的行为。在对象层次结构中实现IEquatable<T>
两次允许并且暗示在系统中有两种不同的表达相等的方式。设置IEquatable<T>
和Object.Equals
会有所不同,因为有多个IEquatable<T>
实现但只有一个Object.Equals
,因此很容易设计。因此,上述操作会失败并在您的代码中造成一些混乱。
有些人可能会争辩说,在对象层次结构中的更高点实现IEquatable<T>
是有效的,因为您想要比较对象属性的子集。在这种情况下,您应该使用专门用于比较这些属性的IEqualityComparer<T>
。
答案 1 :(得分:5)
我一般建议不要实施IEquatable&lt; T&gt;在任何非密封类上,或在大多数情况下实现非通用IComparable,但对于IComparable&lt; T&gt;同样不能说。有两个原因:
在可继承类中实现非泛型IComparable可能比IComparable&lt; T&gt;的实现更值得怀疑。可能最好的做法是允许基类实现它,如果不期望任何子类需要一些其他顺序,但子类不重新实现或覆盖父类实现。
答案 2 :(得分:1)
我见过的大多数Equals
实现检查被比较对象的类型,如果它们不相同则方法返回false。
这巧妙地避免了子类型与其父类型进行比较的问题,从而无需密封类。
一个明显的例子是尝试将2D点(A)与3D点(B)进行比较:对于2D,3D点的x和y值可能相等,但对于3D点, z值很可能会有所不同。
这意味着A == B
将成立,但B == A
将为假。大多数人都喜欢Equals
运算符是可交换的,在这种情况下检查类型显然是一个好主意。
但是如果你是子类并且你没有添加任何新属性呢?嗯,这有点难以回答,可能取决于你的情况。
答案 3 :(得分:0)
我今天在阅读时偶然发现了这个话题
https://blog.mischel.com/2013/01/05/inheritance-and-iequatable-do-not-mix/
我同意,有理由不实施IEquatable<T>
,因为有可能以错误的方式进行操作。
但是,在阅读了链接文章之后,我测试了自己的实现,并在各种非密封的继承类中使用了该实现,并且发现它运行正常。
在实现IEquatable<T>
时,我提到了这篇文章:
http://www.loganfranken.com/blog/687/overriding-equals-in-c-part-1/
它很好地解释了Equals()
中使用什么代码。尽管它不解决继承问题,所以我自己对其进行了调整。这是结果。
并回答原始问题:
我不是说应该在非密封类上实现,但是我说它绝对可以毫无问题地实现。
//============================================================================
class CBase : IEquatable<CBase>
{
private int m_iBaseValue = 0;
//--------------------------------------------------------------------------
public CBase (int i_iBaseValue)
{
m_iBaseValue = i_iBaseValue;
}
//--------------------------------------------------------------------------
public sealed override bool Equals (object i_value)
{
if (ReferenceEquals (null, i_value))
return false;
if (ReferenceEquals (this, i_value))
return true;
if (i_value.GetType () != GetType ())
return false;
return Equals_EXEC ((CBase)i_value);
}
//--------------------------------------------------------------------------
public bool Equals (CBase i_value)
{
if (ReferenceEquals (null, i_value))
return false;
if (ReferenceEquals (this, i_value))
return true;
if (i_value.GetType () != GetType ())
return false;
return Equals_EXEC (i_value);
}
//--------------------------------------------------------------------------
protected virtual bool Equals_EXEC (CBase i_oValue)
{
return i_oValue.m_iBaseValue == m_iBaseValue;
}
}
//============================================================================
class CDerived : CBase, IEquatable<CDerived>
{
public int m_iDerivedValue = 0;
//--------------------------------------------------------------------------
public CDerived (int i_iBaseValue,
int i_iDerivedValue)
: base (i_iBaseValue)
{
m_iDerivedValue = i_iDerivedValue;
}
//--------------------------------------------------------------------------
public bool Equals (CDerived i_value)
{
if (ReferenceEquals (null, i_value))
return false;
if (ReferenceEquals (this, i_value))
return true;
if (i_value.GetType () != GetType ())
return false;
return Equals_EXEC (i_value);
}
//--------------------------------------------------------------------------
protected override bool Equals_EXEC (CBase i_oValue)
{
CDerived oValue = i_oValue as CDerived;
return base.Equals_EXEC (i_oValue)
&& oValue.m_iDerivedValue == m_iDerivedValue;
}
}
测试:
private static void Main (string[] args)
{
// Test with Foo and Fooby for verification of the problem.
// definition of Foo and Fooby copied from
// https://blog.mischel.com/2013/01/05/inheritance-and-iequatable-do-not-mix/
// and not added in this post
var fooby1 = new Fooby (0, "hello");
var fooby2 = new Fooby (0, "goodbye");
Foo foo1 = fooby1;
Foo foo2 = fooby2;
// all false, as expected
bool bEqualFooby12a = fooby1.Equals (fooby2);
bool bEqualFooby12b = fooby2.Equals (fooby1);
bool bEqualFooby12c = object.Equals (fooby1, fooby2);
bool bEqualFooby12d = object.Equals (fooby2, fooby1);
// 2 true (wrong), 2 false
bool bEqualFoo12a = foo1.Equals (foo2); // unexpectedly "true": wrong result, because "wrong" overload is called!
bool bEqualFoo12b = foo2.Equals (foo1); // unexpectedly "true": wrong result, because "wrong" overload is called!
bool bEqualFoo12c = object.Equals (foo1, foo2);
bool bEqualFoo12d = object.Equals (foo2, foo1);
// own test
CBase oB = new CBase (1);
CDerived oD1 = new CDerived (1, 2);
CDerived oD2 = new CDerived (1, 2);
CDerived oD3 = new CDerived (1, 3);
CDerived oD4 = new CDerived (2, 2);
CBase oB1 = oD1;
CBase oB2 = oD2;
CBase oB3 = oD3;
CBase oB4 = oD4;
// all false, as expected
bool bEqualBD1a = object.Equals (oB, oD1);
bool bEqualBD1b = object.Equals (oD1, oB);
bool bEqualBD1c = oB.Equals (oD1);
bool bEqualBD1d = oD1.Equals (oB);
// all true, as expected
bool bEqualD12a = object.Equals (oD1, oD2);
bool bEqualD12b = object.Equals (oD2, oD1);
bool bEqualD12c = oD1.Equals (oD2);
bool bEqualD12d = oD2.Equals (oD1);
bool bEqualB12a = object.Equals (oB1, oB2);
bool bEqualB12b = object.Equals (oB2, oB1);
bool bEqualB12c = oB1.Equals (oB2);
bool bEqualB12d = oB2.Equals (oB1);
// all false, as expected
bool bEqualD13a = object.Equals (oD1, oD3);
bool bEqualD13b = object.Equals (oD3, oD1);
bool bEqualD13c = oD1.Equals (oD3);
bool bEqualD13d = oD3.Equals (oD1);
bool bEqualB13a = object.Equals (oB1, oB3);
bool bEqualB13b = object.Equals (oB3, oB1);
bool bEqualB13c = oB1.Equals (oB3);
bool bEqualB13d = oB3.Equals (oB1);
// all false, as expected
bool bEqualD14a = object.Equals (oD1, oD4);
bool bEqualD14b = object.Equals (oD4, oD1);
bool bEqualD14c = oD1.Equals (oD4);
bool bEqualD14d = oD4.Equals (oD1);
bool bEqualB14a = object.Equals (oB1, oB4);
bool bEqualB14b = object.Equals (oB4, oB1);
bool bEqualB14c = oB1.Equals (oB4);
bool bEqualB14d = oB4.Equals (oB1);
}