- PVSM.RU - https://www.pvsm.ru -
Ранее мы рассмотрели [1] корректную реализацию минимально необходимого набора доработок класса для сравнения объектов класса по значению.
Теперь рассмотрим Type-specific реализацию сравнения объектов по значению, включающую реализацию Generic-интерфейса IEquatable(Of T) [2] и перегрузку операторов "==" и "!=".
Type-specific сравнение объектов по значению позволяет достичь:
Кроме того, реализация Type-specific сравнения по значению необходима по причинам:
Реализация одновременно всех способов сравнения сопряжена определенными с трудностями, т.к. для корректной работы требуется обеспечить:
Рассмотрим реализацию сравнения объектов по значению с учетом вышеизложенных условий, на примере класса Person.
Сразу приведем окончательный вариант кода с пояснениями, почему это сделано именно так, и как именно это работает.
(Демонстрация вывода решения с учетом каждого нюанса содержит слишком много итераций.)
Итак, класс Person с реализацией полного набора способов сравнения объектов по значению:
using System;
namespace HelloEquatable
{
public class Person : IEquatable<Person>
{
protected static string NormalizeName(string name) => name?.Trim() ?? string.Empty;
protected 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);
}
public override int GetHashCode() =>
this.FirstName.GetHashCode() ^
this.LastName.GetHashCode() ^
this.BirthDate.GetHashCode();
protected static bool EqualsHelper(Person first, Person second) =>
first.BirthDate == second.BirthDate &&
first.FirstName == second.FirstName &&
first.LastName == second.LastName;
public virtual bool Equals(Person other)
{
//if ((object)this == null)
// throw new InvalidOperationException("This is null.");
if ((object)this == (object)other)
return true;
if ((object)other == null)
return false;
if (this.GetType() != other.GetType())
return false;
return EqualsHelper(this, other);
}
public override bool Equals(object obj) => this.Equals(obj as Person);
public static bool Equals(Person first, Person second) =>
first?.Equals(second) ?? (object)first == (object)second;
public static bool operator ==(Person first, Person second) => Equals(first, second);
public static bool operator !=(Person first, Person second) => !Equals(first, second);
}
}
Метод Person.GetHashCode() вычисляет хеш-код объекта, основываясь на полях, сочетание которых образует уникальность значения конкретного объекта.
Особенности вычисления хеш-кодов и требования к перекрытию метода Object.GetHashCode() [5] приведены в документации [5], а также в первой публикации [6].
Статический protected метод-хелпер EqualsHelper(Person, Person) сравнивает два объекта по полям, сочетание значений которых образует уникальность значения конкретного объекта.
Виртуальный метод Person.Equals(Person) реализует интерфейс IEquatable(Of Person).
(Метод объявлен виртуальным, т.к. его перекрытие понадобится при наследовании — будет рассмотрено ниже.)
Метод Person.Equals(Object), реализован как вызов метода Person.Equals(Person) с приведением входящего объекта к типу Person с помощью оператора as [13].
Примечание. Если типы объектов не совместимы, то результатом приведения будет null [7], что приведет к получению результата сравнения объектов в методе Person.Equals(Person) на втором шаге (объекты не равны).
Для поддержки статического сравнения объектов по значению для CLS [4]-совместимых языков, не поддерживающих операторы или их перегрузку, реализован статический метод Person.Equals(Person, Person).
(В качестве Type-specific, и более быстродействующей, альтернативы методу Object.Equals(Object, Object) [16].)
(О необходимости реализации методов, соответствующих операторам, и рекомендации по соответствию операторов и имен методов, можно прочесть в книге Джеффри Рихтера (Jeffrey Richter) CLR via C# (Part II "Designing Types", Chapter 8 "Methods", Subchapter "Operator Overload Methods").)
Итак, мы нашли корректный и достаточно компактный способ реализации в одном классе всех способов сравнения объектов класса по значению, и даже учли корректность поведения на случай наследования, заложили в коде возможности, которые сможем использовать при наследовании.
При этом необходимо отдельно рассмотреть, как для данного варианта реализации сравнения объектов по значению корректно выполнить наследование, если в класс наследник вносится поле, входящее в множество полей объекта, образующих уникальное значение объекта:
Пусть есть класс PersonEx, наследующий класс Person, и имеющий дополнительное свойство MiddleName.
В этом случае сравнение двух объектов класса PersonEx:
John Teddy Smith 1990-01-01
John Bobby Smith 1990-01-01
любым реализованным способом даст результат "объекты равны", что неверно с предметной точки зрения.
Таким образом, при кажущейся тривиальности задачи, помимо достаточно больших затрат и рисков, реализация сравнения объектов по значению в текущей инфраструктуре .NET, чревата еще и тем, что как только в классе реализовано сравнение объектов по значению, то реализацию сравнения придется "тащить" (и делать это правильным образом) в классы-наследники, что несет дополнительные затраты и потенциал ошибок.
Как решение этой задачи сделать, насколько возможно, легким и компактным, поговорим в продолжении.
Автор: sand14
Источник [18]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/209580
Ссылки в тексте:
[1] рассмотрели: https://habrahabr.ru/post/314500/
[2] IEquatable(Of T): https://msdn.microsoft.com/library/ms131187.aspx
[3] EqualityComparer(Of T).Default: https://msdn.microsoft.com/library/ms224763.aspx
[4] CLS: https://msdn.microsoft.com/library/12a7a7h3.aspx
[5] Object.GetHashCode(): https://msdn.microsoft.com/library/system.object.gethashcode.aspx
[6] первой публикации: https://habrahabr.ru/post/314328/
[7] null: https://msdn.microsoft.com/library/edakx9da.aspx
[8] InvalidOperationException: https://msdn.microsoft.com/library/system.invalidoperationexception.aspx
[9] ==: https://msdn.microsoft.com/library/53k8ybth.aspx
[10] !=: https://msdn.microsoft.com/library/3tz250sf.aspx
[11] object: https://msdn.microsoft.com/library/9kkx3h3c.aspx
[12] Object.ReferenceEquals(Object, Object): https://msdn.microsoft.com/library/system.object.referenceequals.aspx
[13] as: https://msdn.microsoft.com/library/cscsdfbt.aspx
[14] .NET: https://www.microsoft.com/net
[15] NullReferenceException: https://msdn.microsoft.com/library/system.nullreferenceexception.aspx
[16] Object.Equals(Object, Object): https://msdn.microsoft.com/library/w4hkze5k.aspx
[17] !: https://msdn.microsoft.com/library/f2kd6eb2.aspx
[18] Источник: https://habrahabr.ru/post/315168/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.