229

I want my Food class to be able to test whenever it is equal to another instance of Food. I will later use it against a List, and I want to use its List.Contains() method. Should I implement IEquatable<Food> or just override Object.Equals()? From MSDN:

This method determines equality by using the default equality comparer, as defined by the object's implementation of the IEquatable.Equals method for T (the type of values in the list).

So my next question is: which functions/classes of the .NET framework make use of Object.Equals()? Should I use it in the first place?

silkfire
  • 24,585
  • 15
  • 82
  • 105
devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • 1
    possible duplicate of [Understanding IEquatable](http://stackoverflow.com/questions/411500/understanding-iequatable) – nawfal Oct 08 '13 at 05:19

4 Answers4

264

The main reason is performance. When generics were introduced in .NET 2.0 they were able to add a bunch of neat classes such as List<T>, Dictionary<K,V>, HashSet<T>, etc. These structures make heavy use of GetHashCode and Equals. But for value types this required boxing. IEquatable<T> lets a structure implement a strongly typed Equals method so no boxing is required. Thus much better performance when using value types with generic collections.

Reference types don't benefit as much but the IEquatable<T> implementation does let you avoid a cast from System.Object which can make a difference if it's called frequently.

As noted on Jared Parson's blog though, you must still implement the standard Object.Equals and Object.GetHashcode overrides.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Josh
  • 68,005
  • 14
  • 144
  • 156
  • Is there any cast between reference types? I always thought that casts were just "statements" you make to the compiler when you assign some not obvious casts from one kind of object to another. This is, after you compile, that the code wouldn't even know there was a cast there. – devoured elysium Apr 29 '10 at 05:41
  • 9
    That is true in C++ but not .NET languages which enforce type safety. There is a runtime cast and if the cast does not succeed, an exception is thrown. So there is a small runtime penalty to pay for casting. The compiler can optimize away upcasts For example object o = (object)"string"; But downcasting - string s = (string)o; - must happen at runtime. – Josh Apr 29 '10 at 05:58
  • 1
    I see. By chance you have any place where I can get that kind of "deeper" information about .NET? Thanks! – devoured elysium Apr 29 '10 at 06:03
  • 9
    I would recommend CLR via C# by Jeff Richter and C# in Depth by Jon Skeet. As for blogs, Wintellect blogs are good, msdn blogs, etc. – Josh Apr 29 '10 at 06:15
  • 2
    Does the `IEquatable` interface do anything more than remind a developer to include a `public bool Equals(T other)` member in the class or struct? The presence or absence of the interface makes no difference at run-time. The overload of `Equals` would appear to be all that is necessary. – mikemay Jun 21 '20 at 08:59
  • 1
    @mikemay it makes a difference for the classes that look for it, usually [EqualityComparer.Default](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.default?view=net-5.0), which is the canonical default comparer in basically every BCL class that needs one (like `HashSet`) – Ohad Schneider Nov 10 '21 at 23:11
  • 1
    [As mentioned here](https://learn.microsoft.com/en-us/dotnet/standard/collections/comparisons-and-sorts-within-collections#check-for-equality) If the collection is generic, then items are compared for equality by `IEquatable` first and if not implemented by `object.Equals()` . – Hamed Mar 30 '22 at 06:03
56

According to the MSDN:

If you implement IEquatable<T>, 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<T>.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. This ensures that all invocations of the Equals method return consistent results.

So it seems that there's no real functional difference between the two except that either could be called depending on how the class is used. From a performance standpoint, it's better to use the generic version because there's no boxing/unboxing penalty associated with it.

From a logical standpoint, it's also better to implement the interface. Overriding the object doesn't really tell anyone that your class is actually equatable. The override may just be a do nothing class or a shallow implementation. Using the interface explicitly says, "Hey, this thing is valid for equality checking!" It's just better design.

user11336341
  • 95
  • 1
  • 3
CodexArcanum
  • 3,944
  • 3
  • 35
  • 40
  • 10
    Structs should definitely implement iEquatable(of theirOwnType) if they are going to used as keys in a Dictionary or similar collection; it will offer a major performance boost. Non-inheritable classes will receive a slight performance boost by implementing IEquatable(of theirOwnType). Inheritable classes should //not// implement IEquatable. – supercat Dec 07 '10 at 16:34
36

Extending what Josh said with a practical example. +1 to Josh - I was about to write the same in my answer.

public abstract class EntityBase : IEquatable<EntityBase>
{
    public EntityBase() { }

    #region IEquatable<EntityBase> Members

    public bool Equals(EntityBase other)
    {
        //Generic implementation of equality using reflection on derived class instance.
        return true;
    }

    public override bool Equals(object obj)
    {
        return this.Equals(obj as EntityBase);
    }

    #endregion
}

public class Author : EntityBase
{
    public Author() { }
}

public class Book : EntityBase
{
    public Book() { }
}

This way, I have re-usable Equals() method that works out of the box for all my derived classes.

this. __curious_geek
  • 42,787
  • 22
  • 113
  • 137
  • Just one more question. What is the advantage of using "obj as EntityBase" instead of (EntityBase)obj? Just a matter of style or is there any advantage at all? – devoured elysium Apr 29 '10 at 05:44
  • 24
    in case of "obj as EntityBase" - if obj is not of type EntityBase, it will pass "null" and continue without any error or exception, But in case of "(EntityBase)obj", it will forcefully try to cast the obj to EntityBase and if the obj is not of type EntityBase, it will throw InvalidCastException. And yes, "as" can only be applied to reference types. – this. __curious_geek Apr 29 '10 at 06:05
  • I understand the answer (generic equals - so strongly typed, and no need of converting to object etc.). But have a question regarding following statement: >>> This way, I have re-usable Equals() method that works out of the box for all my derived classes<<< Isn't it true, if one doesn't implement 'Iequatable', just override 'equals' , this can work for derived types as well, right? just want to make sure what your intent is :) – Dreamer Feb 23 '14 at 10:35
  • 1
    Josh's link to Jared Par's blog seems to suggest you also need to override GetHashCode. Is this not the case? – Amicable Apr 07 '14 at 09:25
  • 3
    I don't really get the extra value your implementation provides. Can you clarify the problem that your abstract base class solves? – Mert Akcakaya Jan 06 '15 at 09:35
  • 1
    @Amicable - yes, whenever you override Object.Equals(Object), you must also override GetHashCode so that containers work. – namford May 08 '15 at 15:02
2

If we call object.Equals, it forces to expensive boxing on value types. This is undesirable in performance-sensitive scenarios. The solution is to use IEquatable<T>.

public interface IEquatable<T>
{
  bool Equals (T other);
}

The idea behind IEquatable<T> is that it gives the same result as object.Equals but more quickly. The constrain where T : IEquatable<T> must be used with generic types like below.

public class Test<T> where T : IEquatable<T>
{
  public bool IsEqual (T a, T b)
  {
    return a.Equals (b); // No boxing with generic T
  }
}

otherwise, it binds to slower object.Equals().