What's wrong with defining operator == but not defining Equals() or GetHashCode()?

时光总嘲笑我的痴心妄想 提交于 2019-11-28 09:42:51

EDIT: This answer has been corrected, among other things to note that user-defined value types don't generate ==, and to mention the performance issues with ValueType.Equals.


In general, overridding one, but not all, is confusing. The user expects neither to be overridden, or both to be, with the same semantics.

Microsoft's recommendations for this state (among other things):

  • Implement the GetHashCode method whenever you implement the Equals method. This keeps Equals and GetHashCode synchronized.

  • Override the Equals method whenever you implement the equality operator (==), and make them do the same thing.

In your case, you have a valid reason to defer to Equals (the compiler doesn't automatically implement ==) and override only those two (==/!=). However, there's still a performance issue, since ValueType.Equals uses reflection:

"Override the Equals method for a particular type to improve the performance of the method and more closely represent the concept of equality for the type."

Thus, it's still recommended to override all (==/!=/Equals) in the end. Of course, performance may not matter for this trivial struct.

There is a general expectation within the Framework that certain operations should always produce the same result. The reason is that certain operations (in particular, sorting and searching, which make up a large portion of any application) rely on these different operations producing meaningful and consistent results. In this case, you are breaking a couple of those assumptions:

  • If there is a valid operation == between a and b, it should produce the same result as a.Equals(b)
  • Similar, if there is a valid operation != between a and b, it should produce the same result as !a.Equals(b)
  • If two objects a and b exist, for which a == b, then a and b should produce the same key when stored in a hash table.

The first two, IMO, are obvious; if you are defining what it means for two objects to be equal, you should include all of the ways you can check for two objects to be equal. Note that the compiler doesn't (in general, cannot) enforce that you actually follow those rules. It's not going to perform complex code analysis of the body of your operators to see if they already mimic Equals because, in the worst case, that could be equivalent to solving the halting problem.

What it can do, however, is check for cases where you most likely are breaking those rules, in particular, you provided custom comparison operators and did not provide a custom Equals method. The assumption here is that you would not have bothered to provide operators if you did not want them to do something special, in which case, you should have provided custom behavior for all of the methods that need to be in sync.

If you did implement Equals to be something different from == the compiler would not complain; you would have hit the limit of how hard C# is willing to try to prevent you from doing something stupid. It was willing to stop you from accidentally introducing subtle bugs in your code, but it's going to let you purposely do so if that's what you want.

The third assumption has to do with the fact that many internal operations in the Framework use some variant of a hash table. If I have two objects that are, by my definition, "equal", then I should be able to do this:

if (a == b)
{
    var tbl = new HashTable();
    tbl.Add(a, "Test");

    var s = tbl[b];
    Debug.Assert(s.Equals("Test"));
}

This is a basic property of hash tables that would cause very strange problems if it were suddenly not true.

My guess is your are getting these warning because compiler doesn't know that you use Equals in == method

Suppose you have this implementation

public struct  Person
{
    public int ID;
    public static bool operator ==(Person a, Person b) { return Math.Abs(a.ID - b.ID) <= 5; }
    public static bool operator !=(Person a, Person b) { return Math.Abs(a.ID - b.ID) > 5; }
}

Then

 Person p1 = new Person() { ID = 1 };
 Person p2 = new Person() { ID = 4 };

 bool b1 = p1 == p2;
 bool b2 = p1.Equals(p2);

b1 would be true, but b2 false

--EDIT--

Now suppose you want to do this

Dictionary<Person, Person> dict = new Dictionary<Person, Person>();
dict.Add(p1, p1);
var x1 = dict[p2]; //Since p2 is supposed to be equal to p1 (according to `==`), this should return p1

But this would throw an exception something like KeyNotFound

But if you add

public override bool Equals(object obj)
{
    return Math.Abs(ID - ((Person)obj).ID) <= 5; 
}
public override int GetHashCode()
{
    return 0;
}

you will get what you want.

The compiler just warns you that you can face with similar conditions

All you need to do is add another member to your struct say Forename.

So if you has two persons with an ID of 63 but different forenames, are they equal or not?

All depends on what definition of "same" you want to implement.

Use a better example struct, write a noddy applictaion to execute the various methods and see what happens when you change the definitions of equality and or equivalence, if they aren't all in step, you end up with things like !(a == b) != (a != b), which might be true, but if you don't override all the methods whoever uses you code will wonder what your intent was.

Basically the compiler is telling you to be good citizen and make your intent clear.

Probably because the default Equals() method is not expected to be good enough for a real system (e.g. in your class it should compare the ID field).

Read the MSDN pages.

CS0660

CS0661

The compiler is basically saying: "Since you are saying know how to compare your object, you should make it compare that way all the time."

If you override Equals and GetHashCode you wouldn't even need to override the operators, and that's a cleaner approach. Edited: it should work since this is a struct.

public struct Coord
{
    public int x;
    public int y;

    public Coord(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public static bool operator ==(Coord c1, Coord c2)
    {
        return c1.x == c2.x && c1.y == c2.y;
    }

    public static bool operator !=(Coord c1, Coord c2)
    {
        return !(c1 == c2);
    }

    public bool Equals(Coord other)
    {
        return x == other.x && y == other.y;
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        return obj is Coord && Equals((Coord) obj);
    }

    public override int GetHashCode()
    {
        return 0;
    }
}

Here's an example. Hopefully, it's helpful.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!