57

What does IEquatable<T> buy you, exactly? The only reason I can see it being useful is when creating a generic type and forcing users to implement and write a good equals method.

What am I missing?

Palec
  • 12,743
  • 8
  • 69
  • 138
Jack Kada
  • 24,474
  • 29
  • 82
  • 106
  • 1
    It save you boxing/unboxing handling. Here good and simple explanation of that :http://www.codeproject.com/Articles/20592/Implementing-IEquatable-Properly – Jviaches Apr 05 '15 at 07:07

4 Answers4

52

From the MSDN:

The IEquatable(T) interface is used by generic collection objects such as Dictionary(TKey, TValue), List(T), and LinkedList(T) when testing for equality in such methods as Contains, IndexOf, LastIndexOf, and Remove.

The IEquatable<T> implementation will require one less cast for these classes and as a result will be slightly faster than the standard object.Equals method that would be used otherwise. As an example see the different implementation of the two methods:

public bool Equals(T other) 
{
  if (other == null) 
     return false;

  return (this.Id == other.Id);
}

public override bool Equals(Object obj)
{
  if (obj == null) 
     return false;

  T tObj = obj as T;  // The extra cast
  if (tObj == null)
     return false;
  else   
     return this.Id == tObj.Id;
}
shA.t
  • 16,580
  • 5
  • 54
  • 111
Cornelius
  • 3,526
  • 7
  • 37
  • 49
  • 4
    Does anyone know what happens if you do not implement iequtalable when using generic collections? – Jack Kada Mar 20 '10 at 09:42
  • 2
    @ChloeRadshaw it will use a reference equal for reference types then. – Cornelius Mar 24 '10 at 06:10
  • 1
    It will work with the above types without implementing IEquatable; it will simply use the default Equals() instead. That said, it sounds like it will use IEquatable if available, and avoid the boxing/unboxing that Drew Freyling mentions. – Doug Jul 05 '11 at 19:54
  • GetHashCode must be overriden, or work with lists and other collections will be bad – Siarhei Kuchuk Mar 06 '16 at 21:21
  • Isn't the first null check technically redundant? – Pharap Feb 09 '17 at 18:27
  • Regarding the quote, while the MSDN page still claims that, when I looked at the [source for dictionary](https://referencesource.microsoft.com/#mscorlib/system/collections/generic/dictionary.cs) and list, I can not find a single mention of `IEquatable` being used in those collections. The comparer `EqualityComparer.Default` seems to be used instead (which does not use `IEquatable` either). I am very confused by what the docs mean by that statement :/ – felix May 09 '20 at 04:47
  • 1
    @felix https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.default?view=net-5.0 `EqualityComparer.Default` uses `IEquatable.Equals` – Matus Mar 16 '21 at 05:27
49

I'm amazed that the most important reason is not mentioned here.

IEquatable<> was introduced mainly for structs for two reasons:

  1. For value types (read structs) the non-generic Equals(object) requires boxing. IEquatable<> lets a structure implement a strongly typed Equals method so that no boxing is required.

  2. For structs, the default implementation of Object.Equals(Object) (which is the overridden version in System.ValueType) performs a value equality check by using reflection to compare the values of every field in the type. When an implementer overrides the virtual Equals method in a struct, the purpose is to provide a more efficient means of performing the value equality check and optionally to base the comparison on some subset of the struct's field or properties.

Both of which improves performance.

Reference types (read classes) don't benefit as much. The IEquatable<> implementation does let you avoid a cast from System.Object but that's a very trivial gain. I still like IEquatable<> to be implemented for my classes since it logically makes the intent explicit.

nawfal
  • 70,104
  • 56
  • 326
  • 368
  • 1
    Why do you need IEquatable<> to stop boxing? You could just create your own method bool Equals(StructNameHere yourStruct){...} which would stop the boxing. – David Klempfner Apr 08 '18 at 00:49
  • 3
    @Backwards_Dave sure, but 1, `IEquatable` makes the intent more clear (consider a referenced lib class whose source you dont have), 2, you could pass your structs to methods that accepts `IEquatable` or have generic constrain on `IEquatable`, 3, most importantly, most implementations of various collection operations like `Contains` etc wont call your custom `Equals` (they all check if the type implements `IEquatbale`, or else calls the base `ValueType.Equals`). – nawfal Apr 08 '18 at 05:12
  • Regarding last point, framework could have made use of duck typing just to check for an implementation of `Equals(T)` but it would add further performance cost. In programming generally things go by convention. In .NET you implement `IEquatable` if you want to modify equality of structs (not to forget implementing `GetHashCode`, `==`, `!=`) to go along with it. – nawfal Apr 08 '18 at 05:15
  • 1
    See the difference in behaviour of `EquatableStruct` and `PlainStruct` here: https://dotnetfiddle.net/FTzETU – nawfal Apr 09 '18 at 19:13
3

Further to the other answers here's a very good reason to be implementing IEquatable<T> (and obviously overriding Equals(object) too) for value types. Just look at the default ValueType.Equals(object) code that gets called otherwise. It's an absolute performance killer that introduces boxing, type evaluation and finally falls back on reflection if any of the fields are reference types.

public override bool Equals(object obj)
{
    if (obj == null)
    {
        return false;
    }
    RuntimeType type = (RuntimeType) base.GetType();
    RuntimeType type2 = (RuntimeType) obj.GetType();
    if (type2 != type)
    {
        return false;
    }
    object a = this;
    if (CanCompareBits(this))
    {
        return FastEqualsCheck(a, obj);
    }
    FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    for (int i = 0; i < fields.Length; i++)
    {
        object obj3 = ((RtFieldInfo) fields[i]).UnsafeGetValue(a);
        object obj4 = ((RtFieldInfo) fields[i]).UnsafeGetValue(obj);
        if (obj3 == null)
        {
            if (obj4 != null)
            {
                return false;
            }
        }
        else if (!obj3.Equals(obj4))
        {
            return false;
        }
    }
    return true;
}

In certain scenarios (such as using the value type as a key in a dictionary) it can murder performance in one foul swoop.

0b101010
  • 756
  • 6
  • 15
0

As per the documentation IEquality<T> is used to improve performance (it prevents boxing.) Especially usefull in generic collections.

If you want to implement IEquatable<T> in a class hierarchy you can use the following pattern. It prevents derived (including sibling) classes from being equal. If equality is not needed for the derived class you can skip IEquatable<Derived> but you need to override the CanEqual to prevent it being equal with base classes (unless of course they should be considered equal).

Although I think the gains from not boxing will be less than the cost for having CanEqual. In that case you should seal your types and you no longer need CanEqual. Sealing also has some performance benefits.

public class Base : IEquatable<Base>
{
    protected virtual bool CanEqual(Base other) => other is Base;

    public override bool Equals(object obj) => obj is Base other && Equals(other);

    public bool Equals(Base other) => this.CanEqual(other) && other.CanEqual(this) /* && base logic */; 
}

public class Derived : Base, IEquatable<Derived>
{
    protected override bool CanEqual(Base other) => other is Derived;

    public override bool Equals(object obj) => obj is Derived other && Equals(other);

    public bool Equals(Derived other) => this.CanEqual(other) && other.CanEqual(this) /* && derived logic */;
}
Wouter
  • 2,540
  • 19
  • 31