Preferring EqualityComparer to IEqualityComparer

前端 未结 4 478
伪装坚强ぢ
伪装坚强ぢ 2020-12-05 06:54

From the IEqualityComparer remarks section on MSDN:

  1. We recommend that you derive from the EqualityComparer class instead of

相关标签:
4条回答
  • 2020-12-05 07:08

    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

    0 讨论(0)
  • 2020-12-05 07:11

    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>.

    0 讨论(0)
  • 2020-12-05 07:23

    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.

    0 讨论(0)
  • 2020-12-05 07:24

    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.

    0 讨论(0)
提交回复
热议问题