Предпочитает 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?
Ответы
Ответ 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>
.