Search code examples
c#.netgenericscomparisoniequalitycomparer

EqualityComparer<T>.Default vs. T.Equals


Suppose I've got a generic MyClass<T> that needs to compare two objects of type <T>. Usually I'd do something like ...

void DoSomething(T o1, T o2)
{
  if(o1.Equals(o2))
  {
    ...
  }
}

Now suppose my MyClass<T> has a constructor that supports passing a custom IEqualityComparer<T>, similar to Dictionary<T>. In that case I'd need to do ...

private IEqualityComparer<T> _comparer;
public MyClass() {}
public MyClass(IEqualityComparer<T> comparer)
{
  _comparer = comparer;
}
void DoSomething(T o1, T o2)
{
  if((_comparer != null && _comparer.Equals(o1, o2)) || (o1.Equals(o2)))
  {
    ...
  }
}

To remove this lengthy if statement, it'd be good if I could have _comparer default to a 'default comparer' if the regular constructor is used. I searched for something like typeof(T).GetDefaultComparer() but wasn't able to find anything like it.

I did find EqualityComparer<T>.Default, could I use that? And would then this snippet ...

public MyClass()
{
  _comparer = EqualityComparer<T>.Default;
}
void DoSomething(T o1, T o2)
{
  if(_comparer.Equals(o1, o2))
  {
    ...
  }
}

... provide the same results as using o1.Equals(o2) for all possible cases?

(As a side note, would this mean I'd also need to use any special generic constraint for <T>?)


Solution

  • It should be the same, but it is not guaranteed, because it depends on implementation details of the type T.
    Explanation:
    Without a constraint to T, o1.Equals(o2) will call Object.Equals, even if T implements IEquatable<T>.
    EqualityComparer<T>.Default however, will use Object.Equals only, if T doesn't implement IEquatable<T>. If it does implement that interface, it uses IEquatable<T>.Equals.
    As long as T's implementation of Object.Equals just calls IEquatable<T>.Equals the result is the same. But in the following example, the result is not the same:

    public class MyObject : IEquatable<MyObject>
    {
        public int ID {get;set;}
        public string Name {get;set;}
    
        public override bool Equals(object o)
        {
            var other = o as MyObject;
            return other == null ? false : other.ID == ID;
        }    
    
        public bool Equals(MyObject o)
        {
            return o.Name == Name;
        } 
    }
    

    Now, it doesn't make any sense to implement a class like this. But you will have the same problem, if the implementer of MyObject simply forgot to override Object.Equals.

    Conclusion:
    Using EqualityComparer<T>.Default is a good way to go, because you don't need to support buggy objects!