Вече анализирахме особеностите на структурите на .NET рамката, които представят Типове стойности при сравняване на обекти по стойност – екземпляр на структури.
Сега ще опиша този процес на конкретен пример, за да проверя дали ще ни позволи да определим използването на сравнението на обекти по стойност като цяло и по този начин да опростим извадка от сравняване на обекти по стойност – екземпляри на клас, които представляват референция видове.
Структурата на PersonStruct:
using System; namespace HelloEquatable { public struct PersonStruct : IEquatable<PersonStruct>, IEquatable<PersonStruct?> { private static int GetHashCodeHelper(int[] subCodes) { int result = subCodes[0]; for (int i = 1; i < subCodes.Length; i++) result = unchecked(result * 397) ^ subCodes[i]; return result; } 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() => GetHashCodeHelper( new int[] { 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); } }
Както можете да видите, този пример е по-малък и по-лесен по структура, тъй като екземпляри на структури не са нулеви и не е възможно да се наследяват от дефинирани от потребителя структури. Вече обсъдихме особеностите за прилагане на сравнението по стойност за екземплярите на класа в предишната ми статия.
Освен това сме определили полета за сравнение на обекти, както и внедрихме метода GetHashCode().
Методите и операторите за сравнение са имплементирани в следния ред:
- За да сравним два екземпляра на структури, ние внедрихме статичния метод PersonStruct.Equals(PersonStruct, PersonStruct). Ще използваме този метод като референтен метод за сравнение, когато прилагаме други методи и оператори. Освен това може да се приложи за сравняване на екземпляри на структури на езици, които не поддържат оператори.
- Операторите PersonStruct.==(PersonStruct, PersonStruct) и PersonStruct.!=(PersonStruct, PersonStruct) също са внедрени. Трябва да се отбележи, че C# компилаторът има следните особености:
- Можете да сравните с претоварените оператори T.==(T, T) и T.!=(T, T) в Nullable(Of T)
- Преди да провери равенството на стойностите, компилаторът може да провери дали екземпляри от структури имат валидна стойност. Освен това компилаторът не обвива екземпляри от структури в обекти.
- По този начин, сравняването на екземпляри на структурата Nullable(Of T) с нетипизирана стойност с nullable води до извикване на операторите ==(T, T) или T.!=(T, T), като същевременно се сравняват екземпляри на Nullable( От T) структура без претоварени оператори T.==(T, T) и T.!=(T, T) води до извикване на операторите Object.==(Object, Object) или Object.!=(Object, Object) и в резултат на обвиване на екземпляр в обекта.
- Методът PersonStruct.Equals(PersonStruct) (внедряване на IEquatable(Of PersonStruct)) е реализиран чрез извикване на метода PersonStruct.Equals(PersonStruct, PersonStruct).
- За да избегнем обвиването на екземпляри от структури в обекти, когато имаме един или два екземпляра с Nullable(Of PersonStruct), е възможно да приложим следните методи:
- PersonStruct.Equals(PersonStruct?, PersonStruct?), като извикване на оператора PersonStruct.==(PersonStruct, PersonStruct), се използва за избягване на обвиване на екземпляри от структури на двата аргумента в обекти и извикване на Object.Equals( Object, Object), ако поне един от аргументите е Nullable(Of PersonStruct) екземпляр. Освен това можете да използвате този метод за сравняване на екземпляри с Nullable(Of PersonStruct) на езици, които не поддържат оператори. В кода може да намерите коментари, обясняващи как този метод може да бъде приложен, ако компилаторът на C# не е в състояние да използва операторите T.==(T, T) и T.!=(T, T) за Nullable(Of Т) аргументи.
- PersonStruct.Equals(PersonStruct?) – внедряването на интерфейса IEquatable(Of PersonStruct?), използван за избягване на обвиване на аргументите Nullable(Of PersonStruct) в обекти и извикване на метода PersonStruct.Equals(Object). Реализира се като извикване на оператора PersonStruct.==(PersonStruct, PersonStruct) с коментирания код за използване на оператори T.==(T, T) и T.!=(T, T) за Nullable(Of T) ) аргументи.
- PersonStruct.Equals(Object) – който отменя метода Object.Equals(Object). Реализира се чрез проверка на съвместимостта на тип аргумент с тип на текущия обект, като се използва операторът is чрез прехвърляне на аргумента към PersonStruct и извикване на PersonStruct.Equals(PersonStruct, PersonStruct).
Бележки:
- Имплементирането на интерфейса IEquatable(Of PersonStruct?) — IEquatable(Of Nullable(Of PersonStruct)) служи за показване на конкретни проблеми в платформата при работа със структури, при които обвиването на екземпляри в обекти става по-бързо, отколкото очакваме.
- В реални проекти, при условие че не е необходимо да се подобрява производителността, внедряването на IEquatable(Of Nullable(Of T)) не е приложимо поради архитектурни причини – не трябва да внедряваме въведен IEquatable в типа T за нито един тип.
- По принцип не е необходимо да се претоварва код с различни оптимизации.
За структурите бихме могли да постигнем да направим сравнението по стойност много по-просто и по-продуктивно, като избегнем наследяването на дефинирани от потребителя структури и необходимостта от проверка на обекти на null. Освен това можем да наблюдаваме нова логика, която поддържа аргументи с нула (Of T).
В бъдещата си публикация ще обобщя следните точки:
- Когато е добра идея да се приложи сравнение на обекти по стойност;
- Как можем да опростим прилагането на сравнение по стойност за обекти – екземпляри на клас, които представляват референтни типове.
Прочетете също:
Сравняване на обекти по стойност. Част 1:Начало
Сравняване на обекти по стойност. Част 2:Бележки за прилагане на метода Equals
Сравняване на обекти по стойност. Част 3:Специфични за типа оператори за равенство и равенство
Сравняване на обекти по стойност. Част 4:Оператори за наследяване и сравнение
Сравняване на обекти по стойност. Част 5:Проблем със структурното равенство