Вече анализирахме особеностите на структурите на .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:Проблем със структурното равенство