Методы сравнения ссылочных классов в .Net

327
09 декабря 2016, 08:56

Ранее считал, что переопределять Equals для своих классов можно и нужно. Но натолкнулся на иную информацию, что переопределение может привести к проблемам с некоторыми коллекциями.

  1. Когда следует переопределять object.Equals() для своих классов?

  2. Неужели, если хочется сравнить на эквивалентность согласно сущности два объекта своих классов, нужно создавать отдельный метод?

  3. А как быть со сторонними классами? Получается они не поставляют средств сравнения на эквивалентность сущностей?

Что я понимаю под эквивалентностью сущностей. Допустим есть класс

public class Country
{ 
     public Country (string name)
     {
          this.name = name;
     }
     private readonly string name;
     public string Name { get {return name;}}
}
public void Main()
{
      var c1 = new Country("Россия");
      var c2 = new Country("Россия");
}

так вот c1 и с2 для меня эквивалентны, т.к. не может быть двух стран в моём мире, с одинаковым названием. Просто так получилось, что мы создали два экземпляра, но они идентичны по своей сути..

Answer 1

Как известно, .NET по умолчанию сравнивает объекты ссылочных типов по ссылкам, а объекты значимых типов -- побитово (читай, по значению).

К чему это приводит в вашем примере? К тому, что c1 и c2 считаются неравными. С т.з. бизнес-логики вы правильно заметили, что они равны, однако среда ничего не знает о бизнес-логике.

Отсюда выводы:

  1. Метод Equals() надо переопределять там, где требуется, чтобы объекты считались равными по какому-то определенному правилу. В частности, это нужно, когда вы используете объекты типа в качестве ключей словаря, элементов хэш-сета, а также в качестве элемента какой-либо коллекции и вызываете метод Contains(). Также стоит заметить, что в пару к Equals() нужно переопределять и метод GetHashCode().

  2. Да, нужно. Потому что правила равенства двух объектов одного типа -- это, грубо говоря, бизнес-правила, то, что относится к вашему приложению. Среда исполнения о них ничего не знает, но о них знаете вы как разработчик.

  3. Для кастомного сравнения экземпляров сторонних классов используется интерфейс IEqualityComparer<T> и его реализации. В BCL включены некоторые готовые реализации (например, для сравнения строк без учета регистра). В большинстве случаев вам потребуется создавать свой компаратор.

Answer 2
  1. Стоит переопределять object.Equals() в том случае, если для экземпляров вашего класса есть реально существующий метод определения эквивалентности, общий для всего кода приложения. При переопределении object.Equals() обязательно стоит переопределять и метод object.GetHashCode(), причем так, чтобы для эквивалентных объектов GetHashCode() возвращал одинаковое значение.

  2. Нет, переопределение object.Equals() - не единственный доступный метод. Если нужно задать определение эквивалентности в одном конкретном случае, а не по всему приложению - то можно использовать стороннее сравнение через реализацию IEqualityComparer<T>. Почти все стандартные коллекции позволяют использовать IEqualityComparer<T> - либо как параметр конкретного метода поиска, или как параметр конструктора коллекции.

  3. Для сторонних классов - см. 2.

READ ALSO
Создание кликабельного GameOBject&#39;a

Создание кликабельного GameOBject'a

Имеется набор gameobject'ов который будут работать как кнопкиНа них висит box коллайдер и также кодом вешается pointclick триггер (вызывает функцию...

321
datagridview и зависимые combobox

datagridview и зависимые combobox

Допустим, имеется datagridview, где есть 2 колонки с типом datagridviewcombobox(далее cb)

370
ListView с WrapPanel - как сделать VerticalScrollBar?

ListView с WrapPanel - как сделать VerticalScrollBar?

Имеется ListView следующего вида:

349
Как в C# обходились, когда не было Dynamic?

Как в C# обходились, когда не было Dynamic?

Например, из вне приходит какой-то объект неизвестного типа

311