- PVSM.RU - https://www.pvsm.ru -
В предыдущей публикации [1] мы рассмотрели особенности устройства и работы структур [2] платформы .NET [3], являющихся "типами по значению" (Value Types) [4] в разрезе сравнения по значению объектов — экземпляров структур [2].
Теперь рассмотрим готовый пример реализации сравнения по значению объектов — экземпляров структур [2].
Поможет ли пример для структур [2] более точно определить с предметной (доменной) точки зрения область применимости сравнения объектов по значению в целом, и тем самым упростить образец сравнения по значению объектов — экземпляров классов [5], являющихся ссылочными типами (Reference Types) [6], выведенный в одной из предыдущих публикаций [7]?
using System;
namespace HelloEquatable
{
public struct PersonStruct : IEquatable<PersonStruct>, IEquatable<PersonStruct?>
{
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 PersonStruct(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();
public static bool Equals(PersonStruct first, PersonStruct second) =>
first.BirthDate == second.BirthDate &&
first.FirstName == second.FirstName &&
first.LastName == second.LastName;
public static bool operator ==(PersonStruct first, PersonStruct second) =>
Equals(first, second);
public static bool operator !=(PersonStruct first, PersonStruct second) =>
!Equals(first, second);
public bool Equals(PersonStruct other) =>
Equals(this, other);
public static bool Equals(PersonStruct? first, PersonStruct? second) =>
first == second;
// Alternate version:
//public static bool Equals(PersonStruct? first, PersonStruct? second) =>
// first.HasValue == second.HasValue &&
// (
// !first.HasValue || Equals(first.Value, second.Value)
// );
public bool Equals(PersonStruct? other) => this == other;
// Alternate version:
//public bool Equals(PersonStruct? other) =>
// other.HasValue && Equals(this, other.Value);
public override bool Equals(object obj) =>
(obj is PersonStruct) && Equals(this, (PersonStruct)obj);
// Alternate version:
//public override bool Equals(object obj) =>
// obj != null &&
// this.GetType() == obj.GetType() &&
// Equals(this, (PersonStruct)obj);
}
}
Пример с реализацией сравнения объектов по значению для структур [2] меньше по объему и проще по структуре благодаря тому, что экземпляры структур [2] не могут принимать null [8]-значения и тому, что от структур [2], определенных пользователем (User defined structs), нельзя унаследоваться (особенности реализации сравнения по значению объектов — экземпляров классов, с учетом наследования, рассмотрены в четвертой публикации [7] данного цикла).
Аналогично предыдущим примерам, определены поля для сравнения и реализован метод GetHashCode().
Методы и операторы сравнения реализованы последовательно следующим образом:
Реализован статический метод PersonStruct.Equals(PersonStruct, PersonStruct) для сравнения двух экземпляров структур.
Этот метод будет использован как эталонный способ сравнения при реализации других методов и операторов.
Также этот метод может использоваться для сравнения экземпляров структур в языках, не поддерживающих операторы.
Реализованы операторы PersonStruct.==(PersonStruct, PersonStruct) и PersonStruct.!=(PersonStruct, PersonStruct).
Следует отметить, что компилятор C# имеет интересную особенность:
при наличии у структуры T перегруженных операторов T.==(T, T) и T.!=(T, T), для структур Nullable(Of T) [9] также появляется возможность сравнения с помощью операторов T.==(T, T) и T.!=(T, T).
Вероятно, это "магия" компилятора, проверяющая наличие значения [10] у экземпляров структуры, перед проверкой равенства непосредственно значений [11], и не приводящая к упаковке [12] экземпляров структур в объекты [13].
Что характерно, в этом случае сравнение экземпляра структуры Nullable(Of T) [9] с нетипизированным null [8] также приводит к вызову оператора T.==(T, T) или T.!=(T, T), в то время как аналогичное сравнение экземпляра структуры Nullable(Of T) [9], не имеющей перегруженных операторов T.==(T, T) и T.!=(T, T), приводит к вызову оператора Object.==(Object, Object) или Object.!=(Object, Object) и, как следствие, к упаковке [12] экземпляра структуры объект [13].
Реализован метод PersonStruct.Equals(PersonStruct) (реализация IEquatable(Of PersonStruct)), путем вызова метода PersonStruct.Equals(PersonStruct, PersonStruct).
Метод PersonStruct.Equals(PersonStruct?, PersonStruct?) — для предотвращения упаковки [12] экземпляров структур [2]обоих аргументов в объекты [13] и вызова метода Object.Equals(Object, Object) [14], если хотя бы один из аргументов является экземпляром Nullable(Of PersonStruct).
Также этот метод может быть использован при сравнении экземпляров Nullable(Of PersonStruct) в языках, не поддерживающих операторы.
Метод реализован как вызов оператора PersonStruct.==(PersonStruct, PersonStruct).
Рядом с методом приведен закомментированный код, показывающий, каким образом нужно было бы реализовать этот метод, если бы компилятор C# не поддерживал вышеупомянутую "магию" использования операторов T.==(T, T) и T.!=(T, T) для Nullable(Of T)-аргументов.
Метод PersonStruct.Equals(PersonStruct?) (реализация интерфейса IEquatable(Of PersonStruct?)) — для предотвращения упаковки [12] Nullable(Of PersonStruct)-аргумента в объект [13] и вызова метода PersonStruct.Equals(Object).
Метод также реализован как вызов оператора PersonStruct.==(PersonStruct, PersonStruct), с закомментированным кодом реализации при отсутствии "магии" компилятора.
Для структур [2] исчерпывающая реализация сравнения экземпляров по значению получилась существенно проще и компактнее благодаря отсутствию наследования у User defined structs, а также благодаря отсутствию необходимости проверок на null [8].
(Однако, по сравнению с реализацией для классов, появилась и новая логика, поддерживающая Nullable(Of T) [9]-аргументы).
В следующей публикации мы подведем итоги данного цикла на тему "Object Equality", в т.ч. рассмотрим:
Автор: sand14
Источник [17]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/233366
Ссылки в тексте:
[1] предыдущей публикации: https://habrahabr.ru/post/315622/
[2] структур: https://msdn.microsoft.com/library/ah19swz4.aspx
[3] .NET: https://www.microsoft.com/net
[4] "типами по значению" (Value Types): https://msdn.microsoft.com/library/s1ax56ch.aspx
[5] классов: https://msdn.microsoft.com/library/0b0thckt.aspx
[6] ссылочными типами (Reference Types): https://msdn.microsoft.com/library/490f96s2.aspx
[7] предыдущих публикаций: https://habrahabr.ru/post/315258/
[8] null: https://msdn.microsoft.com/library/edakx9da.aspx
[9] Nullable(Of T): https://msdn.microsoft.com/library/b3h38hb0.aspx
[10] наличие значения: https://msdn.microsoft.com/library/19twx9w9.aspx
[11] значений: https://msdn.microsoft.com/library/ydkbatt6.aspx
[12] упаковке: https://msdn.microsoft.com/library/yz2be5wk.aspx
[13] объекты: https://msdn.microsoft.com/library/9kkx3h3c.aspx
[14] Object.Equals(Object, Object): https://msdn.microsoft.com/library/w4hkze5k.aspx
[15] Object.Equals(Object): https://msdn.microsoft.com/library/bsc2ak47.aspx
[16] is: https://msdn.microsoft.com/library/scekt9xw.aspx
[17] Источник: https://habrahabr.ru/post/319100/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.