Getting equality correct is surprisingly tricky in C#. The landscape is a bit of a confusing jumble:

The == and != operators are a syntactic sugar for static method calls, so it is dispatched based on the static (compile-time) type of both operands.

and operators are a syntactic sugar for static method calls, so it is dispatched based on the static (compile-time) type of both operands. object.Equals(object) is a virtual method, so of course it dispatches on the runtime type of its receiver but not its argument. An override on a type T is required to handle any object as an argument, not just instances of T.

is a virtual method, so of course it dispatches on the runtime type of its receiver but not its argument. An override on a type T is required to handle any object as an argument, not just instances of T. IEquatable<T>.Equals(T) by contrast takes a T.

by contrast takes a T. For reference types, you’ve got to consider comparisons to null even if it is an invalid value.

That’s four ways to implement equality already and we haven’t even gotten into IComparable<T>.CompareTo(T) , which also needs to be able to compute equality.

Moreover: it is legal and surprisingly common for a class to implement == and Equals inconsistently. The Framework Design Guidelines, in section 8.10 actually verges on self-contradiction: it says both that Equals and == should have exactly the same semantics, and then hedges later on the same page to say that for reference types it can be surprising if == means value instead of reference equality. I occasionally see codebases in which == means reference equality and Equals means value equality, though the reverse seems rare. It is then very easy to cause a bug by using reference equality when you mean value equality.

So that’s the equality side of comparison; now let’s talk a bit about IComparable<T> and the inequality operators. Before we go on, if you want a quick refresher on how comparers work, check out my series of articles on buggy comparers.

OK, welcome back. Summing up:

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.

is true then is false but and are true and is zero, and so on. All these operators should consider null as a valid value which is equal to itself and smaller than any other value. This way you can have a List<Natural> and sort it successfully even if there are nulls in there.

and sort it successfully even if there are nulls in there. The CompareTo method returns a negative integer if the receiver is smaller than the argument, a positive integer if it is larger, and zero if they are equal. So I’m going to be stuck having to use int in my program after all, but I will only use three integers, -1, 0 and 1.

The comparison operators should produce a total order. Since the naturals obviously can be put into a consistent order, this will not be difficult.

The easiest way to get all this right in my opinion is to write a single static helper method that does the computation, and then nine entrypoints that simply call the helper method. I’ve made the class implement IEquatable<Natural> and IComparable<Natural> . Here are my entrypoints:

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; }

Notice that this gives you a nice mnemonic for remembering whether a negative number means that the receiver is smaller than the argument or vice versa: x OP y is equivalent to x.CompareTo(y) OP 0 .

I don’t think I need to spell out all the base and recursive cases here; inequality is pretty straightforward.

// negative means x < y // positive means x > y // zero means x == y // two nulls are equal // otherwise, null is always smaller private static int CompareTo(Natural x, Natural y) { if (ReferenceEquals(x, y)) return 0; else if (ReferenceEquals(x, null)) return -1; else if (ReferenceEquals(y, null)) return 1; else if (ReferenceEquals(x, Zero)) return -1; else if (ReferenceEquals(y, Zero)) return 1; else if (x.head == y.head) return CompareTo(x.tail, y.tail); else if (x.head == ZeroBit) return CompareTo(x.tail, y.tail) > 0 ? 1 : -1; else return CompareTo(x.tail, y.tail) < 0 ? -1 : 1; }

C# likes it if you override GetHashCode when you override Equals . As I’ve discussed at length, it is important that any two objects that compare as equal have the same hash code. Since a hash code is a 32 bit integer, this is again of the few places in the series in which I’m forced to use a built-in integer type. Vexing, but I can deal.

It seems reasonable to use the value of the natural, truncated to 32 bits and subject to overflow, as the hash code, and it might come in handy for debugging purposes as well, so let’s make a Natural-to-integer converter:

public override int GetHashCode() { return ToInteger(); } private int ToInteger() { if (ReferenceEquals(this, Zero)) return 0; else return (this.head == ZeroBit ? 0 : 1) + 2 * this.tail.ToInteger(); }

Holy goodness, we got ten public entrypoints into Natural implemented in this episode. Next time on FAIC we’ll slow down a bit and consider just two as we discuss the division and remainder operators in depth.