EqualityComparer <T>.Default vs. T.Equals
Предположим, у меня есть общий MyClass<T>
, который должен сравнивать два объекта типа <T>
. Обычно я бы сделал что-то вроде...
void DoSomething(T o1, T o2)
{
if(o1.Equals(o2))
{
...
}
}
Теперь предположим, что мой MyClass<T>
имеет конструктор, который поддерживает передачу пользовательского IEqualityComparer<T>
, похожего на Dictionary<T>
. В этом случае мне нужно будет...
private IEqualityComparer<T> _comparer;
public MyClass() {}
public MyClass(IEqualityComparer<T> comparer)
{
_comparer = comparer;
}
void DoSomething(T o1, T o2)
{
if((_comparer != null && _comparer.Equals(o1, o2)) || (o1.Equals(o2)))
{
...
}
}
Чтобы удалить этот длинный оператор if, было бы хорошо, если бы у меня был _comparer
по умолчанию "сопоставитель по умолчанию", если используется обычный конструктор. Я искал что-то вроде typeof(T).GetDefaultComparer()
, но не смог найти ничего подобного.
Я нашел EqualityComparer<T>.Default
, могу ли я использовать это? И тогда этот фрагмент...
public MyClass()
{
_comparer = EqualityComparer<T>.Default;
}
void DoSomething(T o1, T o2)
{
if(_comparer.Equals(o1, o2))
{
...
}
}
... дают те же результаты, что и при использовании o1.Equals(o2)
для всех возможных случаев?
(Как побочная заметка, означало бы это также, что мне также нужно использовать любое специальное обобщенное ограничение для <T>
?)
Ответы
Ответ 1
Он должен быть таким же, но он не гарантируется, потому что он зависит от деталей реализации типа T
.
Объяснение:
Без ограничения на T
, o1.Equals(o2) вызовет Object.Equals
, даже если T
реализует IEquatable<T>
.
EqualityComparer<T>.Default
, однако, будет использовать только Object.Equals
, если T
не реализует IEquatable<T>
. Если он реализует этот интерфейс, он использует IEquatable<T>.Equals
.
Пока T
реализация Object.Equals
просто вызывает IEquatable<T>.Equals
, результат будет таким же. Но в следующем примере результат не одинаков:
public class MyObject : IEquatable<MyObject>
{
public int ID {get;set;}
public string Name {get;set;}
public override bool Equals(object o)
{
var other = o as MyObject;
return other == null ? false : other.ID == ID;
}
public bool Equals(MyObject o)
{
return o.Name == Name;
}
}
Теперь нет смысла реализовывать такой класс. Но у вас будет такая же проблема, если разработчик MyObject
просто забыл переопределить Object.Equals
.
Вывод:
Использование EqualityComparer<T>.Default
- хороший способ, потому что вам не нужно поддерживать багги-объекты!
Ответ 2
По умолчанию до переопределения в классе Object.Equals(a,b)
/a.Equals(b)
выполняет сравнение по ссылке.
Какой компаратор будет возвращен EqualityComparer<T>.Default
, зависит от T
. Например, если T : IEquatable<>
, тогда будет создан соответствующий EqualityComparer<T>
.
Ответ 3
Можно использовать оператор null coaelescense? сократить, если это действительно важно
if ((_comparer ?? EqualityComparer<T>.Default).Equals(o1, o2))
{
}
Ответ 4
То, что делает Dictionary<>
и другие общие коллекции в BCL, если вы не укажете компаратора при конструировании объекта. Преимущество этого в том, что EqualityComparer<T>.Default
вернет правильный сопоставитель для типов IEquatable<T>
, типов с нулевым значением и перечислений. Если T ни один из них, он будет выполнять простое сравнение Equals, как это делает старый код.
Ответ 5
Да, я думаю, было бы разумно использовать EqualityComparer<T>.Default
, потому что он использует реализацию IEquatable<T>
, если тип T
реализует его, или переопределение Object.Equals
в противном случае. Вы можете сделать это следующим образом:
private IEqualityComparer<T> _comparer;
public IEqualityComparer<T> Comparer
{
get { return _comparer?? EqualityComparer<T>.Default;}
set { _comparer=value;}
}
public MyClass(IEqualityComparer<T> comparer)
{
_comparer = comparer;
}
void DoSomething(T o1, T o2)
{
if(Comparer.Equals(o1, o2))
{
...
}
}