w0051977
w0051977

Reputation: 15787

When would == be overridden in a different way to .equals?

I understand the difference between == and .equals. There are plenty of other questions on here that explain the difference in detail e.g. this one: What is the difference between .Equals and == this one: Bitwise equality amongst many others.

My question is: why have them both (I realise there must be a very good reason) - they both appear to do the same thing (unless overridden differently).

When would == be overloaded in a different way to how .equals is overridden?

Upvotes: 16

Views: 3122

Answers (4)

Eric Lippert
Eric Lippert

Reputation: 659964

My question is: why have them both (I realise there must be a very good reason)

If there's a good reason it has yet to be explained to me. Equality comparisons in C# are a godawful mess, and were #9 on my list of things I regret about the design of C#:

http://www.informit.com/articles/article.aspx?p=2425867

Mathematically, equality is the simplest equivalence relation and it should obey the rules: x == x should always be true, x == y should always be the same as y == x, x == y and x != y should always be opposite valued, if x == y and y == z are true then x == z must be true. C#'s == and Equals mechanisms guarantee none of these properties! (Though, thankfully, ReferenceEquals guarantees all of them.)

As Jon notes in his answer, == is dispatched based on the compile-time types of both operands, and .Equals(object) and .Equals(T) from IEquatable<T> are dispatched based on the runtime type of the left operand. Why are either of those dispatch mechanisms correct? Equality is not a predicate that favours its left hand side, so why should some but not all of the implementations do so?

Really what we want for user-defined equality is a multimethod, where the runtime types of both operands have equal weight, but that's not a concept that exists in C#.

Worse, it is incredibly common that Equals and == are given different semantics -- usually that one is reference equality and the other is value equality. There is no reason by which the naive developer would know which was which, or that they were different. This is a considerable source of bugs. And it only gets worse when you realize that GetHashCode and Equals must agree, but == need not.

Were I designing a new language from scratch, and I for some crazy reason wanted operator overloading -- which I don't -- then I would design a system that would be much, much more straightforward. Something like: if you implement IComparable<T> on a type then you automatically get <, <=, ==, !=, and so on, operators defined for you, and they are implemented so that they are consistent. That is x<=y must have the semantics of x<y || x==y and also the semantics of !(x>y), and that x == y is always the same as y == x, and so on.

Now, if your question really is:

How on earth did we get into this godawful mess?

Then I wrote down some thoughts on that back in 2009:

https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/

The TLDR is: framework designers and language designers have different goals and different constraints, and they sometimes do not take those factors into account in their designs in order to ensure a consistent, logical experience across the platform. It's a failure of the design process.

When would == be overloaded in a different way to how .equals is overridden?

I would never do so unless I had a very unusual, very good reason. When I implement arithmetic types I always implement all of the operators to be consistent with each other.

Upvotes: 24

Adam Brown
Adam Brown

Reputation: 1729

Generally, you want them to do the same thing, particularly if your code is going to be used by anyone other than yourself and the person next to you. Ideally, for anyone who uses your code, you want to adhere to the principle of least surprise, which having randomly different behaviours violates. Having said this:

Overloading equality is generally a bad idea, unless a type is immutable, and sealed. If you're at the stage where you have to ask questions about it, then the odds of getting it right in any other case are slim. There are lots of reasons for this:

A. Equals and GetHashCode play together to enable dictionaries and hash sets to work - if you have an inconsistent implementation (or if the hash code changes over time) then one of the following can occur:

  • Dictionaries/sets start performing as effectively linear-time lookups.
  • Items get lost in dictionaries/sets

B. What were you really trying to do? Generally, the identity of an object in an object-oriented language IS it's reference. So having two equal objects with different references is just a waste of memory. There was probably no need to create a duplicate in the first place.

C. What you often find when you start implementing equality for objects is that you're looking for a definition of equality that is "for a particular purpose". This makes it a really bad idea to burn your one-and-only Equals for this - much better to define different EqualityComparers for the uses.

D. As others have pointed out, you overload operators but override methods. This means that unless the operators call the methods, horribly amusing and inconsistent results occur when someone tries to use == and finds the wrong (unexpected) method gets called at the wrong level of the hierarchy.

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1499770

== is bound statically, at compile-time, because operators are always static. You overload operators - you can't override them. Equals(object) is executed polymorphically, because it's overridden.

In terms of when you'd want them to be different...

Often reference types will override Equals but not overload == at all. It can be useful to easily tell the difference between "these two references refer to the same object" and "these two references refer to equal objects". (You can use ReferenceEquals if necessary, of course - and as Eric points out in comments, that's clearer.) You want to be really clear about when you do that, mind you.

double has this behavior for NaN values; ==(double, double) will always return false when either operand is NaN, even if they're the same NaN. Equals can't do that without invalidating its contract. (Admittedly GetHashCode is broken for different NaN values, but that's a different matter...)

I can't remember ever implementing them to give different results, personally.

Upvotes: 35

Dan Bryant
Dan Bryant

Reputation: 27495

One case that can come up is when you have a previous codebase that depends on reference equality via ==, but you decide you want to add value equality checking. One way to do this is to implement IEquatable<T>, which is great, but now what about all that existing code that was assuming only references were equal? Should the inherited Object.Equals be different from how IEquatable<T>.Equals works? This doesn't have an easy answer, as ideally you want all of those functions/operators to act in a consistent way.

For a concrete case in the BCL where this happened, look at TimeZoneInfo. In that particular case, == and Object.Equals were kept the same, but it's not clear-cut that this was the best choice.


As an aside, one way you can mitigate the above problem is to make the class immutable. In this case, code is less likely be broken by having previously relied on reference equality, since you can't mutate the instance via a reference and invalidate an equality that was previously checked.

Upvotes: 0

Related Questions