Preferring EqualityComparer to IEqualityComparer

前端 未结 4 497
伪装坚强ぢ
伪装坚强ぢ 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 or not, the Dictionary does that for you.

    If you derive from EqualtityComparer 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, 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 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 : 
        EqualitiyComparer>
    {
        // 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>
            CreateContentComparer(IEqualityComparer valueComparer)
        {
            return new DictionaryComparer(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>
            CreateDefaultValueComparer(IEqualityComparer valueComparer)
        {
            IEqualityComparer defaultValueComparer =
                EqualtiyComparer.Default;
            return new DictionaryComparer(defaultValuecomparer);
        }
    
        // comparer (2): X and Y are equal if equal keys and values are same object
        // use reference equal for values
        public IEqualityComparer CreateReferenceValueComparer()
        {
            IEqualityComparer referenceValueComparer = ...
            return new DictionaryComparer(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 valueComparer) : base()
    {   // if no comparer provided, use the default comparer
        if (Object.ReferenceEquals(valueComparer, null))
            this.valueComparer = EqualityComparer.Default;
        else
            this.valueComparer = valueComparer
    }
    
    // comparer for TValue initialized in constructor
    protected readonly IEqualityComparer valueComparer;
    
    public override bool Equals(Dictionary x, Dictionary 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 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

提交回复
热议问题