38

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>?)

abatishchev
  • 98,240
  • 88
  • 296
  • 433
takrl
  • 6,356
  • 3
  • 60
  • 69
  • 2
    Note: If `o1` is null, `o1.Equals(o2)` will fail with the ubiquitous `NullReferenceException`, whereas `EqualityComparer.Default.Equals(o1, o2)` will happily return false. – M.Stramm Nov 03 '16 at 11:30
  • @M.Stramm That's why I said "I'd do something like ...", and not "Here's my production code ..." ;-) – takrl Jul 24 '17 at 08:15

5 Answers5

51

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!

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • 23
    Any type with IEquatable.Equals and object.Equals doing different things is already doomed :) – Marc Gravell May 02 '11 at 13:28
  • Thanks. Depending on my use-case, I could even specify a constraint like `public MyClass where T : IEquatable` then. Good stuff, you've all been extremely helpful. – takrl May 03 '11 at 08:07
  • 2
    That return statement looks better like `return other != null && other.ID == ID;` – nawfal Dec 15 '13 at 10:04
  • 4
    If you override Equals, don't forget to override GetHashCode - http://stackoverflow.com/questions/23757210/c-sharp-dictionary-file-path-custom-equalitycomparer [Compiler Warning (level 3) CS0659)](http://msdn.microsoft.com/en-us/library/xxhbfytk%28v=vs.90%29.aspx) – Philip Pittle May 20 '14 at 10:39
  • 6
    @MarcGravell You cannot avoid it, if you inherit from a class which sealed its `object.Equals` implementation like `DependencyObject`. In those cases it is super important to use `EqualityComparer.Default` to make sure your own implementation of `Equals` is called. – Tim Pohlmann Sep 24 '15 at 09:19
4

By default, until overridden in a class, Object.Equals(a,b)/a.Equals(b) performs comparison by reference.

What comparer will be returned by EqualityComparer<T>.Default depends on T. For example, if T : IEquatable<> then the appropriate EqualityComparer<T> will be created.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
3

Yes, I think it would be wise to use the EqualityComparer<T>.Default, because it uses the implementation of IEquatable<T> if the type T implements it, or the override of Object.Equals otherwise. You could do it as follows:

private IEqualityComparer<T> _comparer;
public IEqualityComparer<T> Comparer
{
    get { return _comparer?? EqualityComparer<T>.Default;}
    set { _comparer=value;}
}
public MyClass(IEqualityComparer<T> comparer)
{  
    _comparer = comparer;
}
void DoSomething(T o1, T o2)
{  
    if(Comparer.Equals(o1, o2))
    {
     ...
    }
}
AbdouMoumen
  • 3,814
  • 1
  • 19
  • 28
2

You could use the null coaelescense operator ?? to shorten the if if it really matters

  if ((_comparer ?? EqualityComparer<T>.Default).Equals(o1, o2))
  {

  }
Marino Šimić
  • 7,318
  • 1
  • 31
  • 61
2

That's exactly what Dictionary<> and other generic collections in the BCL do if you don't specify a comparer when constructing the object. The benefit of this is that EqualityComparer<T>.Default will return the right comparer for IEquatable<T> types, nullable types, and enums. If T is none of those, it will do a simple Equals comparison like you're old code is doing.

abatishchev
  • 98,240
  • 88
  • 296
  • 433