1

For a simple example, assume you have two classes that are different in many ways, but can still be considered "equateable":

class WholeNumber: IEquatable<WholeNumber> {
    int value;

    public override bool Equals(object obj) {
        if (obj is IEquatable<WholeNumber>) {
            IEquatable<WholeNumber> other = (IEquatable<WholeNumber>) obj;
            return other.Equals(this);
        } else {
            return false;
        }
    }

    public bool Equals(WholeNumber other) {
        return this.value == other.value;
    }
}

class Fraction : IEquatable<WholeNumber> {
    WholeNumber numerator;
    WholeNumber denominator;

    public bool Equals(WholeNumber other) {
        if (denominator != 1) {
            // Assume fraction is already reduced
            return false;
        } else {
            return this.numerator.Equals(other);
        }
    }
}

This will allow any object that claims to be equateable to WholeNumber to be passed into the WholeNumber's Equals(object) function and get the desired result without WholeNumber needed to know about any other class.

Is this pattern a good idea? Is using IEquatable with other classes a common (where it makes sence) thing to do?

Cemafor
  • 1,633
  • 12
  • 27

1 Answers1

3

No, this is a bad idea. While infinite recursion does not happen with this code, it is a constant threat when you have some instances of Equals() delegating to others. (If you use this approach then I'd strongly suggest writing many unit tests to make sure that Equals() does what you expect in all cases.)

Note that when a.Equals((object)b) returns true, a.GetHashCode() == b.GetHashCode() must also be true. If you cannot ensure that new WholeNumber(2) and new Fraction(2, 4) have the same hash code, then they should not compare as equal by Equals(object).

The practice I have adopted is that the Equals(object) override only returns true if the argument's type is or derives from the type where the override is declared -- in this case, obj is WholeNumber. If this is true but obj.GetType() != typeof(WholeNumber) then I do call b.Equals(a) so that the more-derived Equals() gets priority.

If you need equality with cousin types, then that is where IEquatable<T> comes in. In this case you would implement IEquatable<Fraction> on WholeNumber too, and it would make perfect sense to delegate to Fraction's implementation of IEquatable<WholeNumber>.Equals() to avoid duplicating that logic. (Providing an implicit conversion from WholeNumber to Fraction might be beneficial here, too.)

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • Wait, obj is being cast to an IEquateable and `this` is a `WholeNumber` so the `Equals(WholeNumber)` should be used. – Cemafor Aug 21 '13 at 22:47
  • Yeah, overload resolution saves you here and you don't get infinite recursion. Nevertheless I agree that this is a bad idea. – Odrade Aug 21 '13 at 23:05
  • 1
    @Cemafor Ah, I see you are right about that. I will amend the answer. – cdhowie Aug 22 '13 at 14:20
  • I get what you are saying about only returning true if the object is of a derived type, but it seems weird that if A inherits from B than `a.Equals(b)` may not equal `b.Equals(a)` – Cemafor Aug 22 '13 at 15:19
  • @Cemafor Yes, my wording in that part is not very clear and doesn't really give the meaning that I intended. I will try to rework that section. – cdhowie Aug 22 '13 at 16:26