EqualityComparer <T>. По умолчанию против T.Equals - PullRequest
30 голосов
/ 02 мая 2011

Предположим, у меня есть общий 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 по умолчанию для '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 ]

40 голосов
/ 02 мая 2011

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

3 голосов
/ 02 мая 2011

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

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

2 голосов
/ 02 мая 2011

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

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

2 голосов
/ 02 мая 2011

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

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

  }
...