Preferring EqualityComparer<T> to IEqualityComparer<T>
Asked Answered
R

4

40

From the IEqualityComparer<T> remarks section on MSDN:

  1. We recommend that you derive from the EqualityComparer<T> class instead of implementing the IEqualityComparer<T> interface, because the EqualityComparer<T> class tests for equality using the IEquatable<T>.Equals method instead of the Object.Equals method. ...

    • I don't understand the quote's argument of why we would should prefer to derive from EqualityComparer<T> class instead of implementing IEqualityComparer<T>. It implies that objects implementing IEqualityComparer<T> will test for equality using Object.Equals, but isn't the whole point of implementing IEqualityComparer<T> when we don't want to test for equality using Object.Equals or IEquatable<T>.Equals?

    • It also implies that if we derive from EqualityComparer<T>, then derived class will test for equality using IEquatable<T>.Equals method. Again, isn't the whole point of deriving from EqualityComparer<T> when we don't want to test for equality using Object.Equals or IEquatable<T>.Equals (since EqualityComparer<T>.Default already test using Object.Equals or IEquatable<T>.Equals)?

  2. ... This is consistent with the Contains, IndexOf, LastIndexOf, and Remove methods of the Dictionary<TKey, TValue> class and other generic collections.

    • I assume most collections in the .NET library test for default equality of elements (i.e. when users don't provide their own custom IEqualityComparer<T> objects to these collections) by calling IEquatable<T>.Equals or Object.Equals (depending on whether or not elements of type T implement IEquatable<T>) via EqualityComparer<T>.Default.

    • Why don't these collections (when testing for default equality) call IEquatable<T>.Equals or Object.Equals directly instead of via EqualityComparer<T>.Default class?

Raynold answered 18/4, 2011 at 18:40 Comment(4)
You've given quotes, but not said where they've come from, which makes it harder to work out the context...Byway
@Jon: From here: msdn.microsoft.com/en-us/library/ms132123.aspxWastage
+1 for the first question, I remember reading that in the docs and not understanding it, but I passed it off as unimportant.Benavidez
I apologize. Thank you Reed and BlueRaya for providing the linkRaynold
R
27

Regarding your first question:

The remarks section for the IEqualityComparer<T> class doesn't really seem to be providing a reason for why you should prefer deriving from the abstract class over the interface, it sounds more like a reason why the equality comparer interface exists in the first place. What it says there is practically useless, it's basically describing what the default implementation is doing. If anything, the "reasoning" they've provided here sound more like a guideline of what your comparers could do and is irrelevant to what it actually does.

Looking at the public/protected interface of the EqualityComparer<T> class, there's only one redeeming quality, it implements the non-generic IEqualityComparer interface. I think what they meant to say that they recommend deriving from it because EqualityComparer<T> actually implements the non-generic IEqualityComparer interface that way your class may be used where the non-generic comparer is required.

It does make more sense in the remarks section for IComparer<T>:

We recommend that you derive from the Comparer<T> class instead of implementing the IComparer<T> interface, because the Comparer<T> class provides an explicit interface implementation of the IComparer.Compare method and the Default property that gets the default comparer for the object.

I suspect it was supposed to say something similar for IEqualityComparer<T> but some ideas were mixed up and ended up with an incomplete description.


Regarding your second question:

A primary goal for the collections found in the library was to be as flexible as possible. One way to get that is to allow custom ways of comparing objects within them by providing a IComparer<T> or IEqualityComparer<T> to do the comparisons. It would be much more easier to get an instance of a default comparer when one was not supplied than it is to do the comparisons directly. These comparers in turn could include the logic necessary to call the appropriate comparisons packaged nicely.

e.g., The default comparers can determine whether T implements IEquatable<T> and call IEquatable<T>.Equals on the object or otherwise use Object.Equals. Better encapsulated here in the comparer than it is potentially repeated in the collections code.

Besides, if they wanted to fall back on calling IEquatable<T>.Equals directly, they would have to add a constraint on T that would make this call possible. Doing so makes it less flexible and negates the benefits of providing the comparer in the first place.

Respectability answered 18/4, 2011 at 19:39 Comment(6)
I like your idea of the implementation of the nongeneric interface - I wish MSDN had expressed it that clearly. In most cases I don't think it's likely to be a significant benefit, but it's a good point :)Byway
Great post and I will mark it as answer regardless of whether you answer this or not: was I correct in assuming that we usually derive from EqualityComparer<T>/EqualityComparer<T> when we don't want to test for equality of T using Object.Equals or IEquatable<T>.Equals?Raynold
@flock: Added my answer to your second question.Respectability
I apologize for not being more clear – I was asking whether assumptions in my first questions ( thus that we usually derive from EqualityComparer<T>/EqualityComparer<T> when we don't want to test for equality of T using Object.Equals or IEquatable<T>.Equals ) were correct. Not that I regret having you answering my second question ;) Anyways, thank you all for your kind helpRaynold
@flock: My mistake, I thought your question in the comment was the same as what you had in the original question. To answer that, I'd say you implement the IEqualityComparer<T> interfaces when you want to compare things that doesn't necessarily implement IEquatable<T> but want to specify how the comparison is made. Not necessarily because you don't want to use the implementation provided by IEquatable<T>.Equals.Respectability
@Jon: I've made a couple more observations that would make my argument even more solid.Respectability
B
4

I don't understand the suggestion for 1. It seems distinctly odd to me.

As for 2 - very often, you end up with a type (such as Dictionary) which has an IEqualityComparer<T>. While the implementation could store a null value and explicitly call Equals itself, it would be a pain to do so - and would also involve significant ugliness to make sure that it didn't box value types implementing IEquatable<T> unnecessarily. Using the interface an EqualityComparer<T>.Default is significantly simpler and more consistent.

Byway answered 18/4, 2011 at 18:48 Comment(4)
"I don't understand the suggestion for 1. It seems distinctly odd to me." I'm not entirely sure what you don't understand: the arguments made by MSDN quote or with my reasoning why MSDN quote doesn't make sense? If the former, do you agree with my reasoning why quote doesn't make sense?Raynold
@flockofcode: MSDN. The arguments don't make sense - I can't see any reason to prefer deriving from EqualityComparer<T> over implementing the interface directly.Byway
May I also ask: "While the implementation could store a null value..." a) What do you mean by store a null value? b) "... and explicitly call Equals itself..." I assume you mean implementation would explicitly call T's IEquatable<>.Equals?Raynold
@flockofcode: I mean that the implementation could have a field for the comparer, and store a null value and treat that differently to the situation where the field value was non-null... then yes, it could case to IEquatable<T>.Byway
H
0

The main reason to derive a class from a base class is that the base class can provide code that you can reuse, so you don't have to write it yourself.

If you'd derive your comparer from the interface, you'd have to create the code that gives you a default comparer yourself (of course only if you'd need it, but hey, everyone wants free functionality!)

Class EqualityComparer uses the factory design pattern.

In Factory pattern, we create object without exposing the creation logic to the client and refer to newly created object using a common interface.

The nice thing is that all users of the EqualityComparer only have to call prperty default, and everything is done for them to create the proper object that exposes the interface IEqualtiyComparer

The advantage of this, is that if you need an IEqualityComparer as a parameter in a function, then you don't have to check whether class T implements IEqualtiy<T> or not, the Dictionary does that for you.

If you derive from EqualtityComparer<T> and make sure that the derived class follows the factory design pattern then switching between several equaltiy comparers is easy.

Besides, as with any factory, you only have to change the parameters of the factory to let if produce completely different equality comparers.

of course you could create an equality comparer factory without deriving from EqualtyComparer<T>, but if you do derive your factory can create one extra type of equality comparers: the default equality comparer, which is the one that uses eiether IEquatable<T> or Object.Equals. You don't have to write any extra code for this, just derive!

Whether you'll find it useful to derive from EqualtyComparer or not, depends on whether you think the factory design pattern is useful.

As an example, suppose you want to check two dictionaries for equality. One could think of several levels of equality:

  1. Dictionary X and Y are equal if they are the same object
  2. X and Y are equal if they have equal keys (using the dictionary key comparer), and if their values are the same object
  3. X and Y are equal if they have they have equal keys (using the dictionary key comparer), and if their values are equal using the default equality comparer for TValue
  4. X and Y are equal if they they have equal keys (using the dictionary key comparer), and equal values using a provided equality comparer for values.

If you derive your dictionary comparer class from EqualityComparer, you already have comparer (1). If the provided TValue comparer is derived from EqualityComparer, there is no real difference between (3) and (4).

So let's derive the factory that can create these four comparers:

class DictionaryComparerFactory<TKey, TValue> : 
    EqualitiyComparer<Dictionary<TKey, TValue>>
{
    // By deriving from EqaulityComparer, you already have comparer (1)
    // via property Default

    // comparer (4):
    // X and Y are equal if equal keys and equal values using provided value comparer
    public static IEqualityComparer<Dictionary<TKey, TValue>>
        CreateContentComparer(IEqualityComparer<TValue> valueComparer)
    {
        return new DictionaryComparer<TKey, TValue>(valueComparer);
    }

    // comparer (3): X and Y equal if equal keys and values default equal
    // use (4) by providing the default TValue comparer
    public static IEqualityComparer<Dictionary<TKey, TValue>>
        CreateDefaultValueComparer(IEqualityComparer<TValue> valueComparer)
    {
        IEqualityComparer<TValue> defaultValueComparer =
            EqualtiyComparer<TValue>.Default;
        return new DictionaryComparer<TKey, TValue>(defaultValuecomparer);
    }

    // comparer (2): X and Y are equal if equal keys and values are same object
    // use reference equal for values
    public IEqualityComparer<TKey, TValue> CreateReferenceValueComparer()
    {
        IEqualityComparer<TValue> referenceValueComparer = ...
        return new DictionaryComparer<TKey, TValue>(referenceValuecomparer);
    }
}

For comparer (2) you can use the reference value comparer as described in stackoverflow IEqualityComparer that uses ReferenceEquals

So now we have four different equality comparers by only providing code for one comparer. The rest is re-used!

This reuse wan't as easy without a factory that creates default comparers

Code for comparer (4): use a provided comparer to check equality for TValue

// constructor
protected DictionaryComparer(IEqualityComparer<TValue> valueComparer) : base()
{   // if no comparer provided, use the default comparer
    if (Object.ReferenceEquals(valueComparer, null))
        this.valueComparer = EqualityComparer<TValue>.Default;
    else
        this.valueComparer = valueComparer
}

// comparer for TValue initialized in constructor
protected readonly IEqualityComparer<TValue> valueComparer;

public override bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
    if (x == null) { return y == null; } 
    if (y == null) return false;
    if (Object.ReferenceEquals(x, y)) return true;
    if (x.GetType() != y.GetType()) return false;

    // now do equality checks according to (4)
    foreach (KeyValuePair<TKey, TValue> xKeyValuePair in x)
    {
        TValue yValue;
        if (y.TryGetValue(xKeyValuePair.Key, out yValue))
        {   // y also has x.Key. Are values equal?
            if (!this.valueComparer.Equals(xKeyValuePair.Value, yValue))
            {   // values are not equal
                return false;
            }
            // else: values equal, continue with next key
        }
        else
        {   // y misses a key that is in x
            return false;
        }
    }

    // if here, all key/values equal
    return true;
}

Now we can simply compare two dictionaries using different comparers:

var dictionaryX = ...
var dictionaryY = ...

var valueComparer1 = ...
var valueComparer2 = ...

var equalityComparer1 = DictionaryComparer<...>.Default();
var equalityComparer2 = DictionaryComparer<...>..CreateDefaultValueComparer();
var equalityComparer3 = DictionaryComparer<...>.CreatereferenceValueComparer();
var equalityComparer4 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer1);
var equalityComparer5 = DictionaryComparer<...>
   .CreateContentComparer(valueCompaerer2);

So the derivation causes that my equality comparer factories always have a proper Defautlt comparer. Saves me in writing the code myself

Helsie answered 28/9, 2017 at 9:11 Comment(1)
A few years late, but wanted to note that the return true in the last comparer should either be return x.Count == y.Count or more ideally the count check would be performed before the loop with an early return. As it stands the y Dictionary could have all the same keys and values, and more and the comparer would return true. It's currently not reflexive, the order of the parameters would matter (one version returning true, the other false). I realize this is just an example and matters not for the OP's question.... but it caught my eye as an errorIndult
L
0

If you change one word in the the MSDN explanation i.e. derive from into use it makes a lot of more sense.

On MSDN: EqualityComparer<T>

We recommend that you (not derive from) use the EqualityComparer<T> class instead of implementing the IEqualityComparer<T> interface, because the EqualityComparer<T> class tests for equality using the IEquatable<T>.Equals method instead of the Object.Equals method. This is consistent with the Contains, IndexOf, LastIndexOf, and Remove methods of the Dictionary class and other generic collections.

Of course this only works if T implements IEquality<T>

Note that oddly enough only Array and List<T> have IndexOf and LastIndexOf method and there are no overloads that take an IEqualityComparer<T> for any of the methods. Where other generic collections have a constructor that takes an IEqualityComparer<T>

On MSDN: Comparer<T>:

We recommend that you (not derive from) use from the Comparer<T> class instead of implementing the IComparer<T> interface, because the Comparer<T> class provides an explicit interface implementation of the IComparer.Compare method and the Default property that gets the default comparer for the object.

Of course this only works if T implements IComparable or IComparable<T>

If T doesn't implement the required interfaces deriving from EqualityComparer<T> or Comparer<T> is usefull because it provides an implementation for the non generic interfaces for free.

On the other hand, implementing IEqualityComparer<T> or IComparer<T> can have a performance benefit because it can skip the calls to IEquatable<T> or IComparable<T>.

Leatrice answered 4/5, 2019 at 20:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.