Предпочитая EqualityComparer <T>IEqualityComparer <T> - PullRequest
34 голосов
/ 18 апреля 2011

Из раздела замечаний IEqualityComparer<T> на MSDN:

  1. Мы рекомендуем вам наследовать класс EqualityComparer вместо реализацииИнтерфейс IEqualityComparer , поскольку класс EqualityComparer проверяет равенство, используя метод IEquatable .Equals вместо метода Object.Equals....

    • Я не понимаю аргумента цитаты о том, почему мы должны предпочесть наследовать от класса EqualityComparer<T> вместо реализации IEqualityComparer<T>.Это подразумевает, что объекты, реализующие IEqualityComparer<T>, будут проверять на равенство, используя Object.Equals, но не является ли смысл реализации IEqualityComparer<T>, когда мы не хотим проверять на равенство, используя Object.Equals или IEquatable<T>.Equals?

    • Это также подразумевает, что если мы наследуем EqualityComparer<T>, то производный класс проверит на равенство, используя метод IEquatable<T>.Equals.Опять же, разве нет смысла выводить из EqualityComparer<T>, когда мы не хотим проверять равенство с помощью Object.Equals или IEquatable<T>.Equals (поскольку EqualityComparer<T>.Default уже проверяет с использованием Object.Equals или IEquatable<T>.Equals)?

  2. ... Это соответствует методам Contains, IndexOf, LastIndexOf и Remove в словарекласс и другие общие коллекции.

    • Я предполагаю, что большинство коллекций в тесте библиотеки .NET для по умолчанию равенство элементов (т.е. когда пользователи не предоставляютих собственные пользовательские объекты IEqualityComparer<T> для этих коллекций), вызывая IEquatable<T>.Equals или Object.Equals (в зависимости от того, используют ли элементы типа T реализовать IEquatable<T>) через EqualityComparer<T>.Default.

    • Почему эти коллекции (при тестировании на по умолчанию равенство) не вызывают IEquatable<T>.Equals или Object.Equals напрямую, а не через EqualityComparer<T>.Default класс?

Ответы [ 4 ]

25 голосов
/ 18 апреля 2011

Относительно вашего первого вопроса:

То, что там написано, практически бесполезно, поскольку оба метода интерфейса IEqualityComparer<T> помечены как абстрактные.У них обоих нет реализации по умолчанию, и вам все равно придется их переопределить.Во всяком случае, «рассуждения», которые они приводят здесь, звучат скорее как ориентир того, что ваши компараторы могли бы сделать, и не имеют отношения к тому, что они на самом деле делают.

Просмотр общественного / защищенногоинтерфейс класса EqualityComparer<T>, есть только одно качество выкупа, он реализует неуниверсальный интерфейс IEqualityComparer.Я думаю, что они хотели сказать, что они рекомендуют это, потому что EqualityComparer<T> фактически реализует неуниверсальный интерфейс IEqualityComparer таким образом, чтобы ваш класс мог использоваться там, где требуется неуниверсальный компаратор.

Это делает большесмысл в разделе замечаний IComparer<T>:

Мы рекомендуем вам наследовать от класса Comparer вместо реализации интерфейса IComparer , потому что ComparerКласс обеспечивает явную реализацию интерфейса метода IComparer.Compare и свойства Default, которое получает компаратор по умолчанию для объекта.

Я подозреваю, что должно было произойти нечто подобное для IEqualityComparer<T>но некоторые идеи смешались и в итоге получили неполное описание.


Относительно вашего второго вопроса:

Основная цель для коллекций, найденных в библиотеке, заключалась в том, чтобывозможный.Одним из способов добиться этого является предоставление пользовательских способов сравнения объектов внутри них, предоставляя IComparer<T> или IEqualityComparer<T> для сравнения.Было бы намного проще получить экземпляр компаратора по умолчанию, если он не был предоставлен, чем непосредственное сравнение.Эти компараторы, в свою очередь, могут включать логику, необходимую для правильного вызова соответствующих сравнений.

Например, компараторы могут определить, является ли T значением IEquatable<T>, и вызвать IEquatable<T>.Equals для объекта или иным образом использовать * 1039.*.Лучше инкапсулируется здесь в компараторе, чем потенциально повторяется в коде коллекций.

Кроме того, если бы они захотели использовать прямой вызов IEquatable<T>.Equals, им пришлось бы добавить ограничение на T, которое бысделать этот звонок возможным.Это делает его менее гибким и сводит на нет преимущества предоставления компаратора в первую очередь.

3 голосов
/ 18 апреля 2011

Я не понимаю предложение для 1. Это кажется мне явно странным.

Что касается 2 - очень часто вы в конечном итоге получаете тип (такой как Dictionary), который имеет IEqualityComparer<T>.Хотя реализация могла бы хранить нулевое значение и явно вызывать саму Equals, это было бы затруднительно, а также требовало бы значительного уродства, чтобы убедиться, что она не упаковывает типы значений, реализующие IEquatable<T> излишне.При использовании интерфейса EqualityComparer<T>.Default значительно значительно проще и последовательнее.

0 голосов
/ 04 мая 2019

Если вы измените одно слово в объяснении MSDN, т. Е. происходит от на , используйте , это имеет больше смысла.

В MSDN: EqualityComparer<T>

Мы рекомендуем (не наследовать) использовать класс EqualityComparer<T> вместо реализации интерфейса IEqualityComparer<T>, поскольку класс EqualityComparer<T> проверяет равенство с помощью IEquatable<T>.Equals метод вместо Object.Equals метода. Это соответствует методам Contains, IndexOf, LastIndexOf и Remove класса Dictionary и других универсальных коллекций.

Конечно, это работает, только если T реализует IEquality<T>

Обратите внимание, что, как ни странно, только Array и List<T> имеют метод IndexOf и LastIndexOf, и нет перегрузок, которые принимают IEqualityComparer<T> для любого из методов. Где другие универсальные коллекции имеют конструктор, который принимает IEqualityComparer<T>

В MSDN: Comparer<T>:

Мы рекомендуем (не наследовать от) использовать из класса Comparer<T> вместо реализации интерфейса IComparer<T>, поскольку класс Comparer<T> обеспечивает явную реализацию интерфейса IComparer.Compare метод и свойство Default, которое получает компаратор по умолчанию для объекта.

Конечно, это работает, только если T реализует IComparable или IComparable<T>

Если T не реализует требуемые интерфейсы, полученные из EqualityComparer<T> или Comparer<T>, полезно, потому что он предоставляет реализацию для неуниверсальных интерфейсов бесплатно.

С другой стороны, реализация IEqualityComparer<T> или IComparer<T> может иметь выигрыш в производительности, поскольку она может пропускать вызовы на IEquatable<T> или IComparable<T>.

0 голосов
/ 28 сентября 2017

Основная причина создания класса из базового класса заключается в том, что базовый класс может предоставлять код, который вы можете использовать повторно, поэтому вам не нужно писать его самостоятельно.

Если вы извлекаете ваш компаратор из интерфейса, вам нужно будет создать код, который даст вам сам компаратор по умолчанию (конечно, только если вам это нужно, но, эй, всем нужна бесплатная функциональность!)

Класс EqualityComparer использует фабричный шаблон проектирования .

В шаблоне Factory мы создаем объект без предоставления клиенту логики создания и обращаемся к вновь созданному объекту с помощью общего интерфейса.

Приятно, что всепользователям EqualityComparer нужно только вызвать prperty default, и все сделано для того, чтобы они создали надлежащий объект, который предоставляет интерфейс IEqualtiyComparer

Преимущество этого состоит в том, что если вам нуженIEqualityComparer в качестве параметра в функции, тогда вам не нужно проверять, реализует ли класс T IEqualtiy<T> или нет, словарь сделает это за вас.

Если вы наследуете от EqualtityComparer<T> и убедитесь, что производный класс следует фабричному шаблону проектирования , тогда переключение между несколькими равными компараторами легко.

Кроме того, как и для любой фабрики, вам нужно всего лишь изменить параметры фабрики, чтобы разрешить, если производятся совершенно разные компараторы равенства.

Конечно, вы можете создать фабрику компаратора равенства, не производя отEqualtyComparer<T>, но если вы производите производную, ваша фабрика может создать один дополнительный тип средств сравнения: средство сравнения по умолчанию, которое использует eiether IEquatable<T> или Object.Equals.Вам не нужно писать никакого дополнительного кода для этого, просто выведите!

Будет ли полезным выводить из EqualtyComparer или нет, зависит от того, считаете ли вы, что шаблон проектирования фабрики полезен.

В качестве примера предположим, что вы хотите проверить два словаря на равенство.Можно подумать о нескольких уровнях равенства:

  1. Словарь X и Y равны, если они являются одним и тем же объектом
  2. X и Y равны, если они имеют равные ключи (используя словарьключ сравнения), и если их значения являются одним и тем же объектом
  3. X и Y равны, если они имеют одинаковые ключи (с помощью словаря сравнения ключей), и если их значения равны, используя для сравнения равенство по умолчанию дляTValue
  4. X и Y равны, если они имеют равные ключи (с помощью словаря сравнения ключей), и равные значения, используя предоставленный сравнитель равенства для значений.

Если выПолучите ваш класс сравнения словаря от EqualityComparer, у вас уже есть Comparer (1).Если предоставленный TValue Comparer является производным от EqualityComparer, между (3) и (4) нет реальной разницы.

Итак, давайте выведем фабрику, которая может создать эти четыре средства сравнения:

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);
    }
}

Для comparer (2) вы можете использовать эталонный сравнитель значений, как описано в stackoverflow IEqualityComparer, который использует ReferenceEquals

Так что теперь у нас есть четыре разных сравнителя равенства, предоставляя код только для одного сравнителя.Остальное используется повторно!

Это повторное использование не будет таким легким без фабрики, которая создает компараторы по умолчанию

Код для компаратора (4): используйте предоставленный компаратор для проверки равенства для 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;
}

Теперь мы можем просто сравнить два словаря, используя разные компараторы:

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

Таким образом, вывод приводит к тому, что на моих фабриках компараторов равенства всегда есть надлежащий компаратор Defautlt.Спасает меня в написании кода сам

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...