5

I have been reading articles and understand interfaces to an extent however, if i wanted to right my own custom Equals method, it seems I can do this without implementing the IEquatable Interface. An example.

using System;
using System.Collections;
using System.ComponentModel;

namespace ProviderJSONConverter.Data.Components
{
    public class Address : IEquatable<Address>
    {
        public string address { get; set; }

        [DefaultValue("")]
        public string address_2 { get; set; }
        public string city { get; set; }
        public string state { get; set; }
        public string zip { get; set; }

        public bool Equals(Address other)
        {
            if (Object.ReferenceEquals(other, null)) return false;
            if (Object.ReferenceEquals(this, other)) return true;
            return (this.address.Equals(other.address)
                && this.address_2.Equals(other.address_2)
                && this.city.Equals(other.city)
                && this.state.Equals(other.state)
                && this.zip.Equals(other.zip));
        }
    }
 }

Now if i dont implement the interface and leave : IEquatable<Address> out of the code, it seems the application operates exactly the same. Therefore, I am unclear as to why implement the interface? I can write my own custom Equals method without it and the breakpoint will hit the method still and give back the same results. Can anyone help explain this to me more? I am hung up on why include "IEquatable<Address>" before calling the Equals method.

devzim
  • 155
  • 1
  • 14
  • 2
    As an aside: now would be a good time to start following .NET naming conventions; equality on mutable types can cause problems; you should override `Equals(object)` and `GetHashCode` as well. – Jon Skeet Sep 03 '15 at 14:50
  • @haim770: The OP isn't overriding `Object.Equals`, so I don't think it's really the same question. – Jon Skeet Sep 03 '15 at 14:50
  • @JonSkeet, It's not the same, but `this.Equals(otherQuestion)` in terms of the missing piece. – haim770 Sep 03 '15 at 14:53
  • 1
    @haim770: No, I don't think so. Put it this way - I can't see the information I provided in my answer here in any of the answers there. – Jon Skeet Sep 03 '15 at 14:54
  • @JonSkeet I read "can right my own custom Equals" as sign of OP understanding basic Object.Equals/Object.GetHashCode usage/overriding... Could be wrong so - feel free to re-open (and possibly edit the post). – Alexei Levenkov Sep 03 '15 at 14:55
  • 1
    I reopened it as the OP didn't override the `Equals` method, so it's not a duplicate. – ken2k Sep 03 '15 at 14:56
  • @ken2k fair point - consider editing the question to match answers... Since OP did not show example of the usage it is kind of confusing. – Alexei Levenkov Sep 03 '15 at 15:12
  • It is not clear whether this is just a snippet or you really didn't implement `Object.Equals` but I posted a boringly long answer. :) I hope that enlightens the scenarios when the different `Equals` methods are used and when to implement them. – György Kőszeg Sep 03 '15 at 16:22

4 Answers4

15

Now if i dont implement the interface and leave : IEquatable out of the code, it seems the application operates exactly the same.

Well, that depends on what "the application" does. For example:

List<Address> addresses = new List<Address>
{
    new Address { ... }
};
int index = addresses.IndexOf(new Address { ... });

... that won't work (i.e. index will be -1) if you have neither overridden Equals(object) nor implemented IEquatable<T>. List<T>.IndexOf won't call your Equals overload.

Code that knows about your specific class will pick up the Equals overload - but any code (e.g. generic collections, all of LINQ to Objects etc) which just works with arbitrary objects won't pick it up.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks for the explanation. Just tested the code out and i can see what you mean. Without the interface, the equals method is not utilized. – devzim Sep 03 '15 at 16:10
  • Justin Weinzimmer@: *"Without the interface, the equals method is not utilized"* - this is a little bit misleading: you can utilize it without the interface if you override `Equals(object)`. Actually, if you implement `IEquatable`, you **must** override `Equals(object)` as well; otherwise, your type will be handled differently in different scenarios. See my comment here: http://stackoverflow.com/a/32380596/5114784 – György Kőszeg Sep 03 '15 at 16:18
  • The way i have written the equals method up top...It wont be utilized, I know about overriding object equals method however, here i am not doing that. – devzim Sep 03 '15 at 17:27
10

The .NET framework has confusingly many possibilities for equality checking:

  1. The virtual Object.Equals(object)
  2. The overloadable equality operators (==, !=, <=, >=)
  3. IEquatable<T>.Equals(T)
  4. IComparable.CompareTo(object)
  5. IComparable<T>.CompareTo(T)
  6. IEqualityComparer.Equals(object, object)
  7. IEqualityComparer<T>.Equals(T, T)
  8. IComparer.Compare(object, object)
  9. IComparer<T>.Compare(T, T)

And I did not mention the ReferenceEquals, the static Object.Equals(object, object) and the special cases (eg. string and floating-point comparison), just the cases where we can implement something.

Additionally, the default behavior of the first two points are different for structs and classes. So it is not a wonder that a user can be confused about what and how to implement.

As a thumb of rule, you can follow the following pattern:

Classes

  • By default, both the Equals(object) method and equality operators (==, !=) check reference equality.
  • If reference equality is not right for you, override the Equals method (and also GetHashCode; otherwise, your class will not be able to be used in hashed collections)
  • You can keep the original reference equality functionality for the == and != operators, it is common for classes. But if you overload them, it must be consistent with Equals.
  • If your instances can be compared to each other in less or greater meaning, implement the IComparable interface. When Equals reports equality, CompareTo must return 0 (again, consistency).

Basically that's it. Implementing the generic IEquatable<T> and Comparable<T> interfaces for classes is not a must: as there is no boxing, the performance gain would be minimal in the generic collections. But remember, if you implement them, keep the consistency.

Structs

  • By default, the Equals(object) performs a value comparison for structs (checks the field values). Though normally this is the expected behavior in case of a value type, the base implementation does this by using reflection, which has a terrible performance. So do always override the Equals(object) in a public struct, even if you implement the same functionality as it originally had.
  • When the Equals(object) method is used for structs, a boxing happens, which have a performance cost (not as bad as the reflection in ValueType.Equals, but it matters). That's why IEquatable<T> interface exists. You should implement it on structs if you want to use them in generic collections. Have I already mentioned to keep consistency?
  • By default, the == and != operators cannot be used for structs so you must overload them if you want to use them. Simply call the strongly-typed IEquatable<T>.Equals(T) implementation.
  • Similarly to classes, if less-or-greater is meaningful for your type, implement the IComparable interface. In case of structs, you should implement the IComparable<T> as well to make things performant (eg. Array.Sort, List<T>.BinarySearch, using the type as a key in a SortedList<TKey, TValue>, etc.). If you overloaded the ==, != operators, you should do it for <, >, <=, >=, too.

A little addendum: If you must use a type that has an improper comparison logic for your needs, you can use the interfaces from 6. to 9. in the list. This is where you can forget consistency (at least considering the self Equals of the type) and you can implement a custom comparison that can be used in hash-based and sorted collections.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
2

If you had overridden the Equals(object obj) method, then it would only be a matter of performances, as noted here: What's the difference between IEquatable and just overriding Object.Equals()?

But as long as you didn't override Equals(object obj) but provided your own strongly typed Equals(Adddress obj) method, without implementing IEquatable<T> you do not indicate to all classes that rely on the implementation of this interface to operate comparisons, that you have your own Equals method that should be used.

So, as John Skeet noted, the EqualityComparer<Address>.Default property used by List<Address>.IndexOf to compare addresses wouldn't be able to know it should use your Equals method.

Community
  • 1
  • 1
ken2k
  • 48,145
  • 10
  • 116
  • 176
2

IEquatable interface just adds Equals method with whatever type we supply in the generic param. Then the funciton overloading takes care of rest.

if we add IEquatable to Employee structure, that object can be compared with Employee object without any type casting. Though the same we can achieved with default Equals method which accepts Object as param, So converting from Object to struct involves Boxing. Hence having IEquatable <Employee> will improve performance.

for example assume we want to compare Employee structure with another employee

if(e1.Equals(e2))
{
   //do some
}

For above example it will use Equals with Employee as param. So no boxing nor unboxing is required

 struct Employee : IEquatable<Employee>
{
    public int Id { get; set; }

    public bool Equals(Employee other)
    {
        //no boxing not unboxing, direct compare
        return this.Id == other.Id;
    }

    public override bool Equals(object obj)
    {
        if(obj is Employee)
        {   //un boxing
            return ((Employee)obj).Id==this.Id;
        }
        return base.Equals(obj);
    }
}

Some more examples:

Int structure implements IEquatable <int>

Bool structure implements IEquatable <bool>

Float structure implements IEquatable <float>

So if you call someInt.Equals(1) it doesn't fires Equals(object) method. it fires Equals(int) method.

CreativeManix
  • 2,162
  • 1
  • 17
  • 29