Общеизвестно, что в объектной модели .NET, как и во многих других программных платформах, сравнивать объекты можно по ссылке и по значению.
По умолчанию два объекта считаются равными, если соответствующие переменные содержат одну и ту же ссылку.
В противном случае объекты считаются неравными.
Однако, может возникнуть ситуация, когда необходимо считать объекты некоторого класса равными, если они определенным образом совпадают по своему содержимому (тождественны).
Пусть есть класс Person, содержащий персональные данные — имя, фамилию, и дату рождения персоны.
На примере этого класса рассмотрим:
- минимально необходимый набор доработок класса для того, чтобы объекты этого класса сравнивались по значению с помощью стандартной инфраструктуры .NET;
- минимально необходимый и достаточный набор доработок, чтобы объекты этого класса всегда сравнивались по значению с помощью стандартной инфраструктуры .NET — если явно не указано, что сравнение должно производиться по ссылке.
Для каждого случая рассмотрим, каким именно образом лучше реализовать сравнение объектов по значению, чтобы получился согласованный и, насколько это возможно, компактный, copy-paste free, производительный код.
Задача является не настолько тривиальной, насколько это может показаться на первый взгляд.
А также рассмотрим, какие улучшения могли бы быть внесены в платформу, чтобы упростить реализацию этой задачи.
using System; namespace HelloEquatable { public sealed class Person { private static string NormalizeName(string name) => name?.Trim() ?? string.Empty; private static DateTime? NormalizeDate(DateTime? date) => date?.Date; public string FirstName { get; } public string LastName { get; } public DateTime? BirthDate { get; } public Person(string firstName, string lastName, DateTime? birthDate) { this.FirstName = NormalizeName(firstName); this.LastName = NormalizeName(lastName); this.BirthDate = NormalizeDate(birthDate); } } }
Если два объекта класса Person сравнивать любым способом:
- методом Object.ReferenceEquals(Object, Object),
- методом Object.Equals(Object),
- методом Object.Equals(Object, Object),
- операторами == или !=,
то объекты будут считаться равными, только если соответствующие переменные содержат одну и ту же ссылку;
при помещении в хеш-наборы (хеш-карты) и словари, объекты так же будут считаться равными только в случае совпадения ссылок.
В этом случае для сравнения объектов по значению в клиентском коде потребуется написать строки вида:
var p1 = new Person("John", "Smith", new DateTime(1990, 1, 1)); var p2 = new Person("John", "Smith", new DateTime(1990, 1, 1)); bool personEquals = p1.BirthDate == p2.BirthDate && p1.FirstName == p2.FirstName && p1.LastName == p2.LastName;
Примечания:
- Класс Person реализован таким образом, что строковые свойства FirstName и LastName всегда не равны null.
Если FirstName или LastName неизвестны (не заданы), то в качестве признака отсутствия значения подойдет пустая строка.
Это позволит избежать исключения NullReferenceException при обращении к свойствам и методам полей FirstName и LastName, а также коллизии при сравнении null и пустой строки (считать ли FirstName у двух объектов равными, если у одного объекта FirstName равен null, а у другого — пустой строке?). - Свойство BirthDate, напротив, реализовано как Nullable(Of T)-структура, т.к. в случае, если дата рождения неизвестна (не задана), то целесообразно сохранить в свойстве именно null-значение, а не особое значение вида 01/01/1900, 01/01/1970, 01/01/0001 или MinValue, как это зачастую делается.
- При сравнении объектов по значению первым реализовано сравнение дат, т.к. сравнение переменных типа дата-время в общем случае будет производиться быстрее, чем сравнение строк.
- Сравнение дат и строк реализовано с помощью оператора равенства, т.к. оператор равенства сравнивает структуры по значению, а для строк оператор равенства перегружен и так же сравнивает строки по значению.
ссылка на оригинал статьи https://habrahabr.ru/post/314328/
Добавить комментарий