Is it important to override Equals if I'm implementing IEquatable<T>?
Asked Answered
P

3

8

I know the importance of overriding GetHashCode when implementing custom equality checks - for which I have implemented IEquality<T> interface, and also the difference between generic and non-generic Equals as discussed here. Now is there a point to override Equals(object t)? Wouldn't everything come under generic Equals(T t)?

public override int GetHashCode() //required for hashsets and dictionaries
{
    return Id;
}

public bool Equals(T other) //IEquatable<T> here
{
    return Id == other.Id;
}

public override bool Equals(object obj) //required??
{
    return Equals(obj as T);
}
Pownall answered 6/12, 2012 at 10:34 Comment(4)
Watch out with your implementation there. In the generic version you should account for other being null as obj as T will result in null if other is not a T.Kolnick
@Kolnick yes, this was just an example. I'm aware. ThanksPownall
Great, glad you're aware. Was more thinking about future visitors which may see this question and assume that's the right way to implement IEquatable<T>, when its missing a vital part.Kolnick
possible duplicate of What's the difference between IEquatable and just overriding Object.Equals()?Pownall
P
3

From msdn:

If you implement IEquatable, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. In addition, you should overload the op_Equality and op_Inequality operators. This ensures that all tests for equality return consistent results.

I could have done a better google search too. Here's a good article on msdn blogs by JaredPar on the subject.

In short, overriding Equals(object obj):

Needed for the static Equals(object obj1, object obj2) method on object class, or for Contains(object item) on ArrayList instance etc..

Accepting this since the link is more thorough on the subject. Thanks to Oded too..

Pownall answered 6/12, 2012 at 10:51 Comment(0)
G
11

Unsealed types should not implement IEquatable<T>, since the only way to ensure (or even make it likely) that derived types will implement it correctly is to implement IEquatable<T>.Equals(T) so as to call Object.Equals(Object). Since whole purpose of IEquatable<T> is to avoid wasting CPU time converting things to Object before comparing them, an implementation which calls Object.Equals(Object) couldn't do anything faster than the default behavior that would be achieved if the interface wasn't implemented.

Sealed class types may achieve a slight performance advantage by implementing IEquatable<T>; the preferred style is to have Object.Equals(Object) try to cast the parameter to T; if the cast succeeds, use the IEquatable<T>.Equals implementation; otherwise return false. In no case should IEquatable.Equals(T) and Object.Equals(Object) yield different results when passed the same object instance.

Structure types may use the same pattern as sealed class types. The method of attempting the cast is a little different, since a failed cast can't return null, but the pattern should still be the same. If a struct implements a mutating interface (as is the case with e.g. List<T>.Enumerator, a struct that implements IEnumerator<T>), the proper behavior of equality comparisons is a bit murky.

Note, btw, that IComparable<T> and IEquatable<T> should be considered independent of each other. While it will often be the case that when X.CompareTo(Y) is zero, X.Equals(Y) will return true, some types may have a natural ordering where two things may be different without either ranking about the other. For example, one might have a NamedThing<T> type which combines a string and a T. Such a type would support a natural ordering on the name, but not necessarily on T. Two instances whose names match but whose T's differ should return 0 for CompareTo, but false for Equals. Because of this, overriding IComparable does not require overriding GetHashCode if Equals is not changed.

Glide answered 6/12, 2012 at 19:47 Comment(5)
excellent insight in general though not precisely on the question, +1-ed. I have a small doubt on your thoughts on iequatable being valid only for sealed types. Let me say I have Person : IEquatable<Person> unsealed which has its on Equals implementation which is just return Id == other.Id considering Id is a field on Person class. Now if I'm deriving Person to Student, and Student doesn't override anything, what's your point about performance loss, and also about non generic Equals being used again? Could you explain this with the above example in a lil more detail?Pownall
I'm thinking if Student has to be compared for equality, wouldn't the same Equals method on Person class be used again? Or you're saying the default non generic reference equality checking Equals of Student will be used?Pownall
@nawfal: If Student does not implement IEquatable<Student>, then a hashed collection of Student would use the virtual Object.Equals(Object) method. If it does implement IEquatable<Student>, then it would have to either have such implementation chain to the base-class IEquatable<Person>.Equals method or hard-code its own copy of that method. The former would negate any performance advantage of implementing IEquatable<Student>, and the latter would risk breaking things if the behavior of IEquatable<Person> changes. The performance advantage of using IEquatable<T> on...Glide
...a struct type is often quite large. The maximum achievable performance advantage with a class type is much, much, smaller. Even a small performance gain in heavily-used code may be worth taking, if it won't require sacrificing <i>anything</i>, but limiting the behavior of derived classes doesn't seem like a win. Further, I would think that a cleaner design might be to have a sealed type which contains an ID and an inheritable PersonInfo object, where the latter object's compare method would do a full comparison, and the former object would specify an invariant that...Glide
...two instances with the same ID must always hold references to PersonInfo instances that compare as equal to each other. That would clarify the relationship between the IDs and the objects' contents, in a way which would continue to hold for derivatives of PersonInfo even if such derivatives add more fields.Glide
P
7

You certainly should override Equals(object t) - otherwise you may get incorrect results when that overload is used. You can't assume that the Equals(T other) is the overload that will be called.

If you don't override it, reference equality will be used, meaning that something like the following will return false:

myObject1.Id = 1;
myObject2.Id = 1;

myObject1.Equals((object)myObject2); // false!

Another possible problem is with inheriting classes - if you compare your type with an inheriting type, this can easily fail.

Parang answered 6/12, 2012 at 10:35 Comment(5)
I'm asking when can non generic Equals be called? Then what's the point of generic Equals? Sorry this answer is not complete I feelPownall
@Pownall - Added detail to my answer.Parang
Ok I get it now. But the shown example is so rare, still I get the point.Pownall
@Pownall - The example was given to illustrate the possible issue.Parang
@Pownall Another possible problem is with inheriting classes - if you compare your type with an inheriting type, this can easily fail. Why is that so? I'd have thought that Equal(T) would be called if you compare types which inherit of T.Wilmington
P
3

From msdn:

If you implement IEquatable, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable.Equals method. If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class. In addition, you should overload the op_Equality and op_Inequality operators. This ensures that all tests for equality return consistent results.

I could have done a better google search too. Here's a good article on msdn blogs by JaredPar on the subject.

In short, overriding Equals(object obj):

Needed for the static Equals(object obj1, object obj2) method on object class, or for Contains(object item) on ArrayList instance etc..

Accepting this since the link is more thorough on the subject. Thanks to Oded too..

Pownall answered 6/12, 2012 at 10:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.