5

Failing to override GetHashCode and Equals when overloading the equality operator causes the compiler to produce warnings. Why would it be a good idea to change the implementation of either? After reading Eric Lippert's blog post on GetHashCode it's seems like there probably aren't many useful alternatives to GetHashCode's base implementation, why does the compiler I encourage you to change it?

ThinkingStiff
  • 64,767
  • 30
  • 146
  • 239
evanmcdonnal
  • 46,131
  • 16
  • 104
  • 115
  • 1
    The compiler doesn't encourage you. Nor do the framework designers. They just give you the option. Feel free to use the default implementations. – David Heffernan May 20 '13 at 18:07
  • 2
    @DavidHeffernan The compiler generating a warning certainly qualifies as "encouragement" in my eyes. – Servy May 20 '13 at 18:08
  • @Servy That's a perverse way to read it. The compiler is saying, if you must do this, do it right. – David Heffernan May 20 '13 at 18:09
  • In what way does Eric's article indicate that there isn't anything useful other than the base implementation? – Servy May 20 '13 at 18:09
  • *"there probably aren't many useful alternatives to the base implementation"* - That is totally wrong. The base implementation is dreadful and you shouldn't use it. And Eric Lippert certainly isn't saying that there are not many useful alternatives! – Matthew Watson May 20 '13 at 18:10
  • @DavidHeffernan It's certainly not *forcing* him to do anything. You can ignore warnings. It's still "encouraging" though, because hopefully you read the warning and considered that it might be valid. How is that *not* encouraging? – Servy May 20 '13 at 18:10
  • @Servy You are reading it wrong. Create a simple class and override nothing. Then see if you get the compiler warnings. You don't. The compiler does not encourage you to override the base implementations. However, if you start doing so, but don't override all that needs to be overridden the compiler warns that you are probably doing it wrong. – David Heffernan May 20 '13 at 18:13
  • 1
    @DavidHeffernan If you override the `==` operator the compiler will *encourage you* to override `Equals` and `GetHashCode`. That's what the OP said. Obviously it doesn't always encourage you to do so in every case; the OP didn't assert it did. The OP asserted it encourages you to do so when you do X, and asked why. Saying, "it doesn't encourage you" is just wrong. It does...(and rightly so) – Servy May 20 '13 at 18:17
  • 1
    @DavidHeffernan I think what you said in your first reply is slightly misleading. Some might read it as meaning "you can ignore the compiler warning", which I'm pretty sure you *didn't* mean. *If* you get that warning, you *shouldn't* ignore it. – Matthew Watson May 20 '13 at 18:17
  • @Servy I think we are disconnecting here. I don't think the compiler encourages you to change any of the three methods in question. However, once you have elected to change one, then it rightly suggests that you change the others. That's what I meant to say, but failed to do so clearly. – David Heffernan May 20 '13 at 18:20
  • @DavidHeffernan `I don't think the compiler encourages you to change any of the three methods in question.` It doesn't *always* encourage you to change any of the methods in question. `"once you have elected to change one, then it rightly suggests that you change the others."` Or "encourages" instead of "suggests", to use the OP's wording. If you do one, the compiler *encourages* you do do the others, which is exactly what the OP said. To say the compiler never encourages you to do so is wrong, it does do so, when you implement only one of the three methods. – Servy May 20 '13 at 18:24
  • @Servy My second comment says exactly that: "The compiler is saying, if you must do this, do it right." And my previous comment says that too. Are we in accord now? – David Heffernan May 20 '13 at 18:26
  • @DavidHeffernan my wording was perhaps unclear but Servy understands what I was getting at. The warning only applies to when you implement the overloaded equality operator. – evanmcdonnal May 20 '13 at 18:27
  • @DavidHeffernan I don't disagree with that half of your comment at all. I disagreed with the first half of it. – Servy May 20 '13 at 18:27

5 Answers5

9

Let's suppose you are implementing a class.

If you are overloading == then you are producing a type that has value equality as opposed to reference equality.

Given that, now the question is "how desirable is it to have a class that implements reference equality in .Equals() and value equality in ==?" and the answer is "not very desirable". That seems like a potential source of confusion. (And in fact, the company that I now work for, Coverity, produces a defect discovery tool that checks to see if you are confusing value equality with reference equality for precisely this reason. Coincidentally I was just reading the spec for it when I saw your question!)

Moreover, if you are going to have a class that implements both value and reference equality, the usual way to do it is to override Equals and leave == alone, not the other way around.

Therefore, given that you have overloaded ==, it is strongly suggested that you also override Equals.

If you are overriding Equals to produce value equality then you are required to override GetHashCode to match, as you know if you've read my article that you linked to.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Well I've never been a huge fan of operator overloading so I'll probably just get rid of it all together now, however, I'm not sure I completely agree with this mentality. The goal was simply to implement value equality for a group of classes that exist for the exact same purpose. Although it is unlikely reference equality will ever be used on any of the fifty or so objects in question I still wouldn't want to make it impossible to do. I do at least agree that overriding `Equals` for value equality might be a better choice. – evanmcdonnal May 20 '13 at 18:32
  • 2
    @evanmcdonnal If you want to implement value equality, you do it via `Equals()` and *not* via `==`. If you implemented value equality only by overriding `==` then all the Linq would fail because it uses `Equals()` not `==` to determine value equality. So do lots of other standard .Net library classes such as `Dictionary<>` – Matthew Watson May 20 '13 at 18:37
  • 1
    Very nice explanation, thanks! The value vs. reference equality talk leaves me wondering why .NET decided to expose string equality through both `Equals` and `==`. My best guess is that it was done to cut down on questions of [this sort](http://stackoverflow.com/q/513832/335858) that come up regularly on the Java side of the fence, but it's hard to be sure without asking someone who has been on the inside of C#. – Sergey Kalinichenko May 20 '13 at 18:40
  • 1
    @GarryVass If you inherit from IEquatable then you *must* implement `Equals()` - but this question is about *not* implementing `Equals()` – Matthew Watson May 20 '13 at 18:41
  • @MatthewWatson that is really the type of information I was looking for. If there were no side effects overriding `Equals` vs overloading `==` would simply be preference. However, you're still assuming my LINQ queries need to have the same definition of equality as I use elsewhere which isn't necessarily true. – evanmcdonnal May 20 '13 at 18:42
  • 1
    @evanmcdonnal If you do not make `Equals()` work like `==` when you override `==`, you will be in for a world of pain somewhere along the road, honestly you will. – Matthew Watson May 20 '13 at 18:44
  • @MatthewWatson yeah, I don't disagree. I'm still curious to hear peoples opinions but I've already decided my implementation isn't going to touch any of the methods in question. – evanmcdonnal May 20 '13 at 18:46
  • 6
    @evanmcdonnal: It is never impossible to use reference equality; just use `Object.ReferenceEquals(x, y)` or `(object)x == (object)y` and you'll get reference equality no matter what the object overloads or overrides. – Eric Lippert May 20 '13 at 19:45
3

If you don't override Equals() when you override == you will have some amazingly bad code.

How would you feel about this happening?

if (x == y)
{
   if (!x.Equals(y))
       throw new InvalidOperationException("Wut?");
}

Here's an example. Given this class:

class Test
{
    public int Value;
    public string Name;

    public static bool operator==(Test lhs, Test rhs)
    {
        if (ReferenceEquals(lhs, rhs))
            return true;

        if (ReferenceEquals(lhs, null) || ReferenceEquals(rhs, null))
            return false;

        return lhs.Value == rhs.Value;
    }

    public static bool operator!=(Test lhs, Test rhs)
    {
        return !(lhs == rhs);
    }
}

This code will behave oddly:

Test test1 = new Test { Value = 1, Name = "1" };
Test test2 = new Test { Value = 1, Name = "2" };

if (test1 == test2)
    Console.WriteLine("test1 == test2"); // This gets printed.
else
    Console.WriteLine("test1 != test2");

if (test1.Equals(test2))
    Console.WriteLine("test1.Equals(test2)");
else
    Console.WriteLine("NOT test1.Equals(test2)"); // This gets printed!

You do NOT want this!

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Actually...that code is valid...although weird. It does serve as a perfectly valid example of the difference between reference and value equality though. X and Y could both be integers with a value of 1, but they are not the same object...although for integers i think the .Equals would have been true, but that's not the point. it depends on what you want to check i suppose...If you want to check if they are equal, and NOT the same thing...this would be the way to do it. – Nevyn May 20 '13 at 18:23
  • 1
    @Nevyn The code will compile, and you're *allowed* to do it, but the point that the compiler as well as Matt is trying to make is that you shouldn't, and that it's not semantically valid even if it's syntactically valid. – Servy May 20 '13 at 18:25
  • here's a scenario. you have an object, and a list of objects. Your object is defined as `thing = list[2]`. Later, when you step through the list...in what way would you check something along these lines. "Perform said action as long as current loop object IS NOT thing (defined previously)". For this one, we dont CARE about value equality, we care about reference equality. And it illustrates why you _might_ want there to be a difference between the two. Its not necessarily required...but I have used code like that before. – Nevyn May 20 '13 at 19:52
  • 1
    @Nevyn Indeed, but if you want there to be a difference you use `Equals()` to mean value equality and leave `==` to mean reference equality, since that's what `==` means by default for reference types. – Matthew Watson May 20 '13 at 20:02
  • Didn't know that, actually, I always thought it was the other way around. Learn something new all the time :-) – Nevyn May 20 '13 at 20:09
  • @Nevyn Maybe you were thinking of `object.ReferenceEquals()`, which *does* compare references (obviously!). – Matthew Watson May 20 '13 at 20:12
  • 1
    If one were designing something like a "Fraction" struct, it might make sense to have the `==` operator regard 3/4 and 6/8 as equal, but have the `Equals` method regard them as distinct. For performance reasons one should override `Equals` and `GetHashCode`, but there's no reason their behavior would have to differ from the default behavior of `ValueType.Equals(Object)`. – supercat Nov 15 '13 at 18:37
1

My guess is that the compiler takes its clues from your actions, and decides that since you find it important to provide an alternative implementation of the equality operator, then you probably want the object equality to remain consistent with your new implementation of ==. After all, you do not want the two equality comparisons to mean drastically different things, otherwise your program would be hard to understand even on a very basic level. Therefore, the compiler thinks that you should redefine Equals as well.

Once you provide an alternative implementation Equals, however, you need to modify GetHashCode to stay consistent with the equality implementation. Hence the compiler warns you that your implementation might be incomplete, and suggests overriding both Equals and GetHashCode.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
0

If you don't overload the Equals method too, then using it might give different results from the ones you'd get with the operator. Like, if you overload = for integers...

int i = 1;
(1 == 1) == (i.Equals(1))

Could evaluate to false.

For the same reason, you should reimplement the GetHashCode method so you don't mess up with hashtables and such other structures that rely on hash comparisons.

Notice I'm saying "might" and "could", not "will". The warnings are there just as a reminder that unexpected things might happen if you don't follow its suggestions. Otherwise you'd get errors instead of warnings.

Geeky Guy
  • 9,229
  • 4
  • 42
  • 62
0

The documentation is pretty clear about this:

The GetHashCode method can be overridden by a derived type. Value types must override this method to provide a hash function that is appropriate for that type and to provide a useful distribution in a hash table. For uniqueness, the hash code must be based on the value of an instance field or property instead of a static field or property.

Objects used as a key in a Hashtable object must also override the GetHashCode method because those objects must generate their own hash code. If an object used as a key does not provide a useful implementation of GetHashCode, you can specify a hash code provider when the Hashtable object is constructed. Prior to the .NET Framework version 2.0, the hash code provider was based on the System.Collections.IHashCodeProvider interface. Starting with version 2.0, the hash code provider is based on the System.Collections.IEqualityComparer interface.

Woot4Moo
  • 23,987
  • 16
  • 94
  • 151