Nel mio sistema utilizzo spesso i codici aeroportuali ( "YYZ"
, "LAX"
, "SFO"
, ecc.), sono sempre nello stesso formato esatto (3 lettere, rappresentate in maiuscolo). Il sistema in genere gestisce 25-50 di questi (diversi) codici per richiesta API, con oltre un migliaio di allocazioni totali, vengono passati attraverso molti livelli della nostra applicazione e vengono confrontati per l'uguaglianza abbastanza spesso.
Abbiamo iniziato con il passaggio delle stringhe, che ha funzionato bene per un po ', ma abbiamo subito notato molti errori di programmazione passando un codice errato in un punto in cui era previsto il codice a 3 cifre. Abbiamo anche riscontrato problemi in cui dovevamo fare un confronto insensibile alle maiuscole e minuscole e invece non ci siamo riusciti, causando bug.
Da questo ho deciso di interrompere il passaggio delle stringhe e creare una classe Airport
, che ha un singolo costruttore che accetta e convalida il codice aeroportuale.
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);
}
}
Questo ha reso il nostro codice molto più facile da capire e abbiamo semplificato i nostri controlli di uguaglianza, usi dizionario / set. Ora sappiamo che se i nostri metodi accettano un'istanza Airport
che si comporterà come ci aspettiamo, ha semplificato i nostri controlli di metodo a un controllo di riferimento null.
La cosa che ho notato, tuttavia, era che la garbage collection era in esecuzione molto più spesso, che ho rintracciato in un sacco di casi in cui Airport
veniva raccolta.
La mia soluzione è stata quella di convertire class
in struct
. Principalmente si trattava solo di una modifica di parole chiave, ad eccezione di GetHashCode
e ToString
:
public override string ToString()
{
return Code ?? string.Empty;
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
Per gestire il caso in cui viene utilizzato default(Airport)
.
Le mie domande:
-
Stavo creando una classe
Airport
o strutturavo una buona soluzione in generale, oppure sto risolvendo il problema sbagliato / risolvendolo nel modo sbagliato creando il tipo? Se non è una buona soluzione, qual è la soluzione migliore? -
Come dovrebbe la mia applicazione gestire le istanze in cui viene utilizzato
default(Airport)
? Un tipo didefault(Airport)
non ha senso per la mia applicazione, quindi ho fattoif (airport == default(Airport) { throw ... }
in luoghi in cui ottenere un'istanza diAirport
(e la sua proprietàCode
) è fondamentale per l'operazione.
Note: Ho esaminato le domande C # / VB struct - come evitare il caso con valori di default zero, che è considerato non valido per la struttura data? , e Usa struct oppure no prima di porre la mia domanda, tuttavia penso che le mie domande siano abbastanza diverse da giustificare il proprio post.