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

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

4b9b3361

Ответ 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))
    {
     ...
    }
}