37

任何人对是否或IEquatable<T>应该IComparable<T>通常要求Tsealed(如果​​是class)有任何意见吗?

这个问题出现在我身上,因为我正在编写一组旨在帮助实现不可变类的基类。基类旨在提供的部分功能是相等比较的自动实现(使用类的字段以及可应用于字段以控制相等比较的属性)。完成后应该会很不错 - 我正在使用表达式树为 each 动态创建编译比较函数T,因此比较函数应该非常接近常规相等比较函数的性能。(我正在使用一个不可变的字典键控System.Type和双重检查锁定来以合理的方式存储生成的比较函数)

但是,出现的一件事是使用什么函数来检查成员字段的相等性。我最初的意图是检查每个成员字段的类型(我将称之为X)是否实现IEquatable<X>。但是,经过一番思考,我不认为这是安全的,除非Xis sealed。原因是 if Xis not sealed,我不能确定 ifX是否适当地将相等检查委托给虚拟方法 on X,从而允许子类型覆盖相等比较。

这就提出了一个更普遍的问题——如果一个类型不是密封的,它真的应该实现这些接口吗?我认为不会,因为我认为接口契约是在两种X类型之间进行比较,而不是两种可能存在或不存在的类型X(尽管它们当然必须X是子类型或子类型)。

你们有什么感想?是否应该避免非密封类IEquatable<T>IComparable<T>(也让我想知道是否有针对此的 fxcop 规则)

我目前的想法是让我生成的比较函数仅用于isIEquatable<T>的成员字段,而不是使用 virtual if即使实现也是未密封的,因为该字段可能存储子类型,我怀疑大多数实现的设计是否适合继承.TsealedObject.Equals(Object obj)TTIEquatable<T>TIEquatable<T>

4

4 回答 4

21

我一直在考虑这个问题,经过一番考虑,我同意实现IEquatable<T>并且IComparable<T>应该只在密封类型上完成。

我来回走了一会儿,但后来我想到了下面的测试。在什么情况下应该返回false?恕我直言,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>专门用于比较这些属性的。

于 2009-12-08T17:21:30.607 回答
5

我通常建议不要在任何非密封类上实现 IEquatable<T>,或者在大多数类上实现非泛型 IComparable,但对于 IComparable<T> 则不能这样说。两个原因:

  1. 已经存在一种比较可能是或可能不是同一类型的对象的方法:Object.Equals。由于 IEquatable<T> 不包括 GetHashCode,因此它的行为本质上必须与 Object.Equals 的行为相匹配。除了 Object.Equals 之外,实现 IEquatable<T> 的唯一原因是性能。与 Object.Equals 相比,IEquatable<T> 在应用于密封类类型时提供了小的性能改进,而在应用于结构类型时提供了很大的改进。然而,IEquatable<T>.Equals 的非密封类型的实现可以确保其行为与可能被覆盖的 Object.Equals 的行为相匹配的唯一方法是调用 Object.Equals。如果 IEquatable<T>.Equals 必须调用 Object.Equals,则任何可能的性能优势都会消失。
  2. 有时,基类具有定义的仅涉及基类属性的自然顺序是可能的、有意义的和有用的,这将在所有子类中保持一致。在检查两个对象是否相等时,结果不应取决于将对象视为基类型还是派生类型。然而,在对对象进行排序时,结果通常应取决于用作比较基础的类型。派生类对象应实现 IComparable<TheirOwnType> 但不应覆盖基类型的比较方法。两个派生类对象在作为父类型比较时比较为“未排序”是完全合理的,但在作为派生类型比较时一个比较在另一个之上是完全合理的。

在可继承类中实现非泛型 IComparable 可能比实现 IComparable<T> 更值得怀疑。如果不期望任何子类需要其他排序,但子类不重新实现或覆盖父类实现,那么最好的办法可能是允许基类实现它。

于 2011-10-07T21:30:09.540 回答
1

Most Equals implementations I've seen check the types of the objects being compared, if they aren't the same then the method returns false.

This neatly avoids the problem of a sub-type being compared against it's parent type, thereby negating the need for sealing a class.

An obvious example of this would be trying to compare a 2D point (A) with a 3D point (B): for a 2D the x and y values of a 3D point might be equal, but for a 3D point, the z value will most likely be different.

This means that A == B would be true, but B == A would be false. Most people like the Equals operators to be commutative, to checking types is clearly a good idea in this case.

But what if you subclass and you don't add any new properties? Well, that's a bit harder to answer, and possibly depends on your situation.

于 2009-12-09T14:29:54.510 回答
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);
}
于 2018-11-29T23:36:00.603 回答