EqualityComparer.По умолчанию против T. равно

Предположим, у меня есть универсальный 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 по умолчанию используется "default 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.
Пока С 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> будет создан.


вы можете использовать оператор null coaelescense ?? чтобы сократить если это действительно имеет значение

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

  }

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

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