- PVSM.RU - https://www.pvsm.ru -

О сравнении объектов по значению — 6: Structure Equality Implementation

В предыдущей публикации [1] мы рассмотрели особенности устройства и работы структур [2] платформы .NET [3], являющихся "типами по значению" (Value Types) [4] в разрезе сравнения по значению объектов — экземпляров структур [2].

Теперь рассмотрим готовый пример реализации сравнения по значению объектов — экземпляров структур [2].

Поможет ли пример для структур [2] более точно определить с предметной (доменной) точки зрения область применимости сравнения объектов по значению в целом, и тем самым упростить образец сравнения по значению объектов — экземпляров классов [5], являющихся ссылочными типами (Reference Types) [6], выведенный в одной из предыдущих публикаций [7]?

struct PersonStruct

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().

Методы и операторы сравнения реализованы последовательно следующим образом:

  1. Реализован статический метод PersonStruct.Equals(PersonStruct, PersonStruct) для сравнения двух экземпляров структур.
    Этот метод будет использован как эталонный способ сравнения при реализации других методов и операторов.
    Также этот метод может использоваться для сравнения экземпляров структур в языках, не поддерживающих операторы.

  2. Реализованы операторы 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].

  3. Реализован метод PersonStruct.Equals(PersonStruct) (реализация IEquatable(Of PersonStruct)), путем вызова метода PersonStruct.Equals(PersonStruct, PersonStruct).

  4. Для предотвращения упаковки [12] экземпляров структур [2] в объект [13], если в сравнении участвует один или два экземпляра Nullable(Of 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), с закомментированным кодом реализации при отсутствии "магии" компилятора.

  • И наконец, реализован метод PersonStruct.Equals(Object), перекрывающий метод Object.Equals(Object) [15].
    Метод реализован путем проверки совместимости типа аргумента с типом текущего объекта с помощью оператора is [16], с последующими приведением аргумента к PersonStruct и вызовом PersonStruct.Equals(PersonStruct, PersonStruct).

Для структур [2] исчерпывающая реализация сравнения экземпляров по значению получилась существенно проще и компактнее благодаря отсутствию наследования у User defined structs, а также благодаря отсутствию необходимости проверок на null [8].
(Однако, по сравнению с реализацией для классов, появилась и новая логика, поддерживающая Nullable(Of T) [9]-аргументы).

В следующей публикации мы подведем итоги данного цикла на тему "Object Equality", в т.ч. рассмотрим:

  • в каких случаях, с предметной и технической точек зрения, действительно целесообразно реализовывать сравнение значений объектов по значению;
  • каким образом в этих случаях возможно упростить реализацию сравнения по значению для объектов — экземпляров классов [5], являющихся ссылочными типами (Reference Types) [6], с учетом опыта упрощенной реализации для структур [2].

Автор: 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