14

I've been looking for an answer on the internet but all I've found was:

Edit: Added some items in response to the answers

  • For IEquatable

    • I'm supposed to overload Equals(), GetHashCode(), == and != together.
    • I'm supposed to reduce redundancy via implementing != via ==.
    • I'm supposed to seal the class
  • For IComparable

    • I'm supposed to overload Equals(), GetHashCode(), <, >, <= and >= together.
    • In fact it is recommend to implement IEquatable when doing so
    • Overload the non-generic version of IComparable
    • CompareTo() == 0 should mean Equals() == true

So I've been thinking about this:

public bool Equals(T other)
{
    if ((object)other == null)
    {
        return false;
    }

    return CompareTo(other) == 0;
}

Am I overlooking something or is this ok?

Johannes
  • 1,599
  • 1
  • 10
  • 10
  • Look at OrangeDog's answer from [this thread](http://stackoverflow.com/questions/16816827/implementing-equals-method-using-compareto). It's for Java, but the problem is similar. – Pajdziu May 16 '15 at 13:48
  • 1
    IEqutable is used for equality while IComprable is used for ordering the objects. Also is the your are implementing both IEquatable and IComparable to the same class ? If yes, for whatever reason, I don't think why it should be problem to reuse the CompareTo for Equality. – Biswanath May 16 '15 at 13:52
  • It is the other way around `Equals() == true` implies `CompareTo() == 0`. The opposite is not always true. – Kryptos May 16 '15 at 14:29
  • I disagree. `Equals() == true` may not be the same as `CompareTo() == 0` - see my answer for details. – Markus Safar May 16 '15 at 14:30
  • Your answer implements a degenerate solution. Equals should be implemented in a way that it tests all relevant fields for equality. Otherwise is does not follow the common definition of equality. – Kryptos May 16 '15 at 14:32
  • 1
    As a side note, [please remember to seal your class if you implement `IEquatable`](http://blog.mischel.com/2013/01/05/inheritance-and-iequatable-do-not-mix/). – dcastro May 16 '15 at 14:35
  • @Kyptos "Your answer implements a degenerate solution." Does refer to my proposed implementation? If yes, does this mean I'm only supposed to sort according to one field and not all at once in a hierarchical way? – Johannes May 16 '15 at 16:16
  • @Biswanath Yes, both interfaces are for the same class. – Johannes May 16 '15 at 16:17
  • @Johannes I think he means that an equality check should compare every field, whereas `CompareTo` does *not* have to check all fields. It *can*, but it doesn't *have to*. – dcastro May 16 '15 at 16:24

3 Answers3

9

According to Eric Lippert, a former developer on the C# compiler team at Microsoft:

  • There are nine ways to do a comparison in C#: < <= > >= == != object.Equals(object) IEquatable<T>.Equals(T) IComparable<T>.CompareTo(T)
  • Ideally these should all be consistent with each other. That is, if x == y is true then x < y is false but x <= y and x.Equals(y) are true and x.CompareTo(y) is zero, and so on.

So, in his opinion, "ideally" x.CompareTo(y) == 0 implies x.Equals(y) == true and vice versa.

Eric then provides an example that implements everything using a private helper method:

public int CompareTo(Natural x) { return CompareTo(this, x); }
public static bool operator <(Natural x, Natural y) { return CompareTo(x, y) < 0; }
public static bool operator >(Natural x, Natural y) { return CompareTo(x, y) > 0; }
public static bool operator <=(Natural x, Natural y) { return CompareTo(x, y) <= 0; }
public static bool operator >=(Natural x, Natural y) { return CompareTo(x, y) >= 0; }
public static bool operator ==(Natural x, Natural y) { return CompareTo(x, y) == 0; }
public static bool operator !=(Natural x, Natural y) { return CompareTo(x, y) != 0; } 
public override bool Equals(object obj) { return CompareTo(this, obj as Natural) == 0; }
public bool Equals(Natural x) { return CompareTo(this, x) == 0; }

private static int CompareTo(Natural x, Natural y) { ... }
Michael Liu
  • 52,147
  • 13
  • 117
  • 150
  • 3
    That's why I prefer using IComparer to move the comparison problem somewhere else. In this way the comparison inside the class could be identical to the check for equality. – Markus Safar May 16 '15 at 16:56
  • As this is closest to an official answer I can get it, I'll take it. Even so I understand the reservations, in my case I'll include all fields anyway. – Johannes May 16 '15 at 20:53
8

x.CompareTo(y) == 0 does not imply x.Equals(y) == true.

x.CompareTo(y) == 0 only means that, when sorting elements x and y, the items appear "in the same position"

For example, given the following class:

public class Person
{
    public string Name { get; set;}
    public string Passport { get; set; }
}

var me = new Person { Name = "Diogo Castro", Passport = "12345" };
var someoneElse = new Person { Name = "Diogo Castro", Passport = "67890" }; 

When sorting these two persons by their name (alphabetically), you'd want me.CompareTo(someoneElse) to return 0, since they share the same name. Therefore, the order in which they appear is irrelevant.

However, you'd want me.Equals(someoneElse) to return false, because they are not the same person.

dcastro
  • 66,540
  • 21
  • 145
  • 155
  • 3
    Hahaha, your example is way better than mine :-) – Markus Safar May 16 '15 at 14:31
  • "`x.CompareTo(y) == 0` implies `x.Equals(y) == true`" is then only correct for Java? – Johannes May 16 '15 at 16:10
  • @Johannes I would argue it's not correct regardless of the language. But to be honest, the fact that the JavaDoc suggests otherwise is intriguing. – dcastro May 16 '15 at 16:20
  • 1
    If `x.CompareTo(y) == 0` does not imply `x.Equals(y)` then I argue the class represents something that is not naturally, inherently sortable as its identity (equality) and it *should not* implement `IComparable`. Even something like calendar events which seem inherently orderable by time, shouldn't be `IComparable` because they have no inherent ordering *precisely for the reason* that two events occurring at the same time are not equal to each other. If "when ordered, items that sort together aren't equal to each other", then that ordering requires a scoping context—use `IComparer` instead. – ErikE Jul 09 '16 at 00:25
  • 1
    Using your own example, a person is not inherently orderable, and it's completely arbitrary whether you order by `Name`, age, height, intelligence, temperature, or something else. Thus, your `Person` class is completely unsuitable for `IComparable` and instead you should have `public class PersonNameComparer : IComparer`. Your example is **not** a counter-example, and instead supports my case that things that compare 0 via `IComparable` should also be equal. – ErikE Jul 09 '16 at 00:29
1

According to a note in IEquatable(T) Interface the IEquatable interface should be used to determine whether two objects are equal or not. According to IComparable(T) Interface the IComparable interface should be used for supporting the comparison of objects in a way to determine the order of them.

So of course you could implement your Equals method as you did but by doing so you create an dependency between two different things. The equality of two objects and their order.

It always depends on how you look at the object itself. It could be (a stupid example, I know) that you specify that two cars are the same if they have the same engine type, same engine power and so on. If you want to order them, you want to order them by maybe comparing just the power of the engine, not the type and everything else. So you could implement Equals to return true if the object itself contains identical values/references and CompareTo if they maybe have only the same power but maybe different engine types, etc..

(Sidenote: That's why I prefer realising comparison with the IComparer(T) Interface.)

Markus Safar
  • 6,324
  • 5
  • 28
  • 44
  • 2
    Equals should test whether tow objects are equals, regardless of their meaning (i.e. tests equality on their inner fields). In your example, Equals does not respect its common definition. – Kryptos May 16 '15 at 14:33
  • True, the example I provided maybe a bad one but I wanted to point out the difference that `Equality` may be not the same as the result `0` from the comparison method. – Markus Safar May 16 '15 at 14:36
  • So I'm supposed not to share code even if it is guaranteed that two objects a and b have `a.CompareTo(b) == 0` as well `a.Equals(b) == true`? Also, I would go the `IComparer` route if I had more than one way of sorting. – Johannes May 16 '15 at 16:05
  • In my opinion you shouldn't because in that way you would mix two different problem aspects, the equality and the comparability. Like dcastro has shown in his example, if the `name` of the person instances are equal you would like them to be *the same* in the comparision but in total the instances are *NOT equal*. If the `passport number` is identical too, they are equal. In addition you could never remove the `IComparable` interface without having any problem. – Markus Safar May 16 '15 at 16:09