EqualityComparer<T>. По умолчанию против 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>?)

5 ответов

Решение

Это должно быть то же самое, но это не гарантируется, потому что это зависит от деталей реализации типа 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 это хороший способ, потому что вам не нужно поддерживать ошибочные объекты!

По умолчанию, пока не переопределено в классе, Object.Equals(a,b)/a.Equals(b) выполняет сравнение по ссылке.

Какой компаратор будет возвращен EqualityComparer<T>.Default зависит от T, Например, если T : IEquatable<> тогда соответствующий EqualityComparer<T> будет создан.

Да, я думаю, что было бы разумно использовать 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))
    {
     ...
    }
}

Вы могли бы использовать нулевой оператор coaelescense?? сократить, если это действительно имеет значение

  if ((_comparer ?? EqualityComparer<T>.Default).Equals(o1, o2))
  {

  }

Это именно то, что Dictionary<> и другие универсальные коллекции в BCL делают, если вы не указываете компаратор при создании объекта. Преимущество этого заключается в том, что EqualityComparer<T>.Default вернет правильный компаратор для IEquatable<T> типы, обнуляемые типы и перечисления. Если T не является ни одним из них, он сделает простое сравнение Equals, как делает ваш старый код.

Другие вопросы по тегам