No meu sistema eu freqüentemente operar com códigos de aeroporto ( "YYZ"
, "LAX"
, "SFO"
, etc.), eles estão sempre no mesmo formato exato (3 letras, representado como maiúsculas). O sistema normalmente lida com 25 a 50 desses códigos (diferentes) por solicitação da API, com mais de mil alocações no total, eles são passados por várias camadas de nosso aplicativo e são comparados quanto à igualdade com bastante frequência.
Começamos apenas passando as strings, o que funcionou bem por um tempo, mas rapidamente notamos muitos erros de programação, passando um código errado em algum lugar em que o código de 3 dígitos era esperado. Também enfrentamos problemas em que deveríamos fazer uma comparação sem distinção entre maiúsculas e minúsculas e, em vez disso, não resultando em erros.
A partir disso, decidi parar de passar as strings e criar uma Airport
classe, que tem um único construtor que pega e valida o código do aeroporto.
public sealed class Airport
{
public Airport(string code)
{
if (code == null)
{
throw new ArgumentNullException(nameof(code));
}
if (code.Length != 3 || !char.IsLetter(code[0])
|| !char.IsLetter(code[1]) || !char.IsLetter(code[2]))
{
throw new ArgumentException(
"Must be a 3 letter airport code.",
nameof(code));
}
Code = code.ToUpperInvariant();
}
public string Code { get; }
public override string ToString()
{
return Code;
}
private bool Equals(Airport other)
{
return string.Equals(Code, other.Code);
}
public override bool Equals(object obj)
{
return obj is Airport airport && Equals(airport);
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
public static bool operator ==(Airport left, Airport right)
{
return Equals(left, right);
}
public static bool operator !=(Airport left, Airport right)
{
return !Equals(left, right);
}
}
Isso tornou nosso código muito mais fácil de entender e simplificamos nossas verificações de igualdade, uso de dicionário / conjunto. Agora sabemos que, se nossos métodos aceitarem uma Airport
instância que se comportará da maneira que esperamos, simplificou nossas verificações de método para uma verificação de referência nula.
O que notei, no entanto, foi que a coleta de lixo estava sendo executada com muito mais frequência, que eu rastreei para muitas instâncias de Airport
coleta.
Minha solução para isso foi converter o arquivo class
em struct
. Principalmente foi apenas uma alteração de palavra-chave, com exceção de GetHashCode
e ToString
:
public override string ToString()
{
return Code ?? string.Empty;
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
Para lidar com o caso em que default(Airport)
é usado.
Minhas perguntas:
A criação de uma
Airport
classe ou estrutura foi uma boa solução em geral, ou estou resolvendo o problema errado / resolvendo-o da maneira errada, criando o tipo? Se não é uma boa solução, qual é a melhor solução?Como meu aplicativo deve lidar com instâncias em que o
default(Airport)
arquivo é usado? Um tipo dedefault(Airport)
é absurdo para o meu aplicativo, então eu tenho feitoif (airport == default(Airport) { throw ... }
em lugares onde obter uma instância deAirport
(e suaCode
propriedade) é fundamental para a operação.
Notas: Revisei as perguntas C # / VB struct - como evitar casos com zero valor padrão, que é considerado inválido para determinada estrutura? , e Use struct ou não antes de fazer minha pergunta, no entanto, acho que minhas perguntas são diferentes o suficiente para garantir sua própria postagem.
default(Airport)
problema é simplesmente desautorizar as instâncias padrão. Você pode fazer isso escrevendo um construtor sem parâmetros e jogando InvalidOperationException
ou NotImplementedException
nele.