Подтвердить что ты не робот

Предпочитает EqualityComparer <T> для IEqualityComparer <T>

Из раздела IEqualityComparer<T> раздела MSDN:

  • Мы рекомендуем, чтобы вы EqualityComparer <T> вместо реализация IEqualityComparer <T> интерфейса, поскольку Тесты класса EqualityComparer <T> для равенства с использованием Метод IEquatable <T> .Equals вместо метод Object.Equals....

    • Я не понимаю аргумент quote о том, почему мы предпочли бы вывести из класса 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)?

  • ... Это согласуется с Содержит, IndexOf, LastIndexOf и Удалите методы словаря < TKey, TValue > класс и другие общие коллекции.

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

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

4b9b3361

Ответ 1

По поводу вашего первого вопроса:

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

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

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

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

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


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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Класс EqualityComparer использует шаблон factory.

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

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

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

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

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

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

Если вам будет полезно извлечь из EqualtyComparer или нет, зависит от того, насколько полезен шаблон дизайна Factory.

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

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

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

Итак, давайте получим Factory, который может создать эти четыре компаратора:

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

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

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

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

Код для сравнения (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);

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

Ответ 4

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

На MSDN: EqualityComparer<T>

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

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

Обратите внимание, что, как ни странно, только у Array и List<T> есть LastIndexOf 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>.