Já analisamos peculiaridades de structs do framework .NET que representam Value Types ao comparar objetos por valor – instância de structs.
Agora, vou descrever esse processo em um exemplo específico para verificar se ele nos permitirá determinar o uso da comparação de objetos por valor em geral e, assim, simplificar uma amostra de comparação de objetos por valor – instâncias de classe que representam referência tipos.
A estrutura 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);
}
} Como você pode ver, este exemplo é menor e mais fácil por estrutura, pois instâncias de structs não são nulas e não é possível herdar de structs definidas pelo usuário. Já discutimos peculiaridades para implementar a comparação por valor para as instâncias de classe em meu artigo anterior.
Além disso, determinamos campos para comparação de objetos e implementamos o método GetHashCode().
Os métodos e operadores de comparação foram implementados na seguinte ordem:
- Para comparar duas instâncias de structs, implementamos o método estático PersonStruct.Equals(PersonStruct, PersonStruct). Usaremos esse método como um método de comparação de referência ao implementar outros métodos e operadores. Além disso, pode ser aplicado para comparar instâncias de structs em linguagens que não suportam operadores.
- Os operadores PersonStruct.==(PersonStruct, PersonStruct) e PersonStruct.!=(PersonStruct, PersonStruct) também foram implementados. Deve-se notar que um compilador C# tem as seguintes peculiaridades:
- Você pode comparar com os operadores sobrecarregados T.==(T, T) e T.!=(T, T) no Nullable(Of T)
- Antes de verificar uma igualdade de valor, um compilador pode verificar se instâncias de structs têm um valor válido. Além disso, um compilador não agrupa instâncias de structs em objetos.
- Assim, comparar instâncias da estrutura Nullable(Of T) com um valor anulável não tipado leva a chamar os operadores ==(T, T) ou T.!=(T, T), enquanto compara instâncias do Nullable( De T) sem operadores sobrecarregados T.==(T, T) e T.!=(T, T) resulta na chamada dos operadores Object.==(Object, Object) ou Object.!=(Object, Object) e, como resultado, no encapsulamento de uma instância no objeto.
- O método PersonStruct.Equals(PersonStruct) (implementação de IEquatable(Of PersonStruct)) foi implementado chamando o método PersonStruct.Equals(PersonStruct, PersonStruct).
- Para evitar envolver instâncias de structs em objetos, quando temos uma ou duas instâncias Nullable(Of PersonStruct), é possível implementar os seguintes métodos:
- PersonStruct.Equals(PersonStruct?, PersonStruct?), como uma chamada do operador PersonStruct.==(PersonStruct, PersonStruct), é usado para evitar envolver instâncias de structs de ambos os argumentos em objetos e chamar o Object.Equals( Object, Object) se pelo menos um dos argumentos for uma instância Nullable(Of PersonStruct). Além disso, você pode usar esse método para comparar instâncias Nullable(Of PersonStruct) em idiomas que não oferecem suporte a operadores. No código, você pode encontrar comentários explicando como esse método pode ser implementado se um compilador C# não puder usar os operadores T.==(T, T) e T.!=(T, T) para o Nullable(Of T) argumentos.
- PersonStruct.Equals(PersonStruct?) – a implementação da interface IEquatable(Of PersonStruct?) usada para evitar envolver os argumentos Nullable(Of PersonStruct) em objetos e chamar o método PersonStruct.Equals(Object). Ele é implementado como uma chamada do operador PersonStruct.==(PersonStruct, PersonStruct) com o código comentado para usar os operadores T.==(T, T) e T.!=(T, T) para o Nullable(Of T) ) argumentos.
- PersonStruct.Equals(Object) – que substitui o método Object.Equals(Object). Ele é implementado verificando a compatibilidade de um tipo de argumento com um tipo do objeto atual usando o operador is lançando o argumento para PersonStruct e chamando PersonStruct.Equals(PersonStruct, PersonStruct).
Notas:
- A implementação da interface IEquatable(Of PersonStruct?) — IEquatable(Of Nullable(Of PersonStruct)) serve para mostrar problemas específicos na plataforma ao trabalhar com structs em que o encapsulamento de instâncias em objetos acontece mais rápido do que o esperado. >
- Em projetos reais, desde que não seja necessário melhorar o desempenho, a implementação de IEquatable(Of Nullable(Of T)) não é aplicável por motivos de arquitetura – não devemos implementar IEquatable tipado no tipo T para nenhum tipo.
- Em geral, não é necessário sobrecarregar um código com otimizações diferentes.
Para estruturas, poderíamos fazer com que a comparação por valor fosse muito mais simples e produtiva, evitando a herança de estruturas definidas pelo usuário e a necessidade de verificar objetos em null. Além disso, podemos monitorar uma nova lógica que suporta argumentos Nullable(Of T).
Em minha futura publicação, resumirei os seguintes pontos:
- Quando é uma boa ideia implementar a comparação de objetos por valor;
- Como podemos simplificar a implementação da comparação por valor para objetos – instâncias de classe que representam tipos de referência.
Leia também:
Comparando objetos por valor. Parte 1:Começo
Comparando objetos por valor. Parte 2:Notas de Implementação do Método Equals
Comparando objetos por valor. Parte 3:Igualdades e Operadores de Igualdade Específicos do Tipo
Comparando objetos por valor. Parte 4:Operadores de Herança e Comparação
Comparando objetos por valor. Parte 5:Questão de Igualdade de Estrutura