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