Dans mon système je fonctionne souvent avec des codes d'aéroport ( "YYZ"
, "LAX"
, "SFO"
, etc.), ils sont toujours dans le même format exact (3 lettres, représentée en majuscules). Le système traite généralement 25 à 50 de ces codes (différents) par demande d'API, avec plus de mille allocations au total, ils sont transmis à travers de nombreuses couches de notre application et sont comparés assez souvent pour l'égalité.
Nous avons commencé par simplement passer des chaînes, ce qui a bien fonctionné un peu, mais nous avons rapidement remarqué beaucoup d'erreurs de programmation en passant un mauvais code quelque part où le code à 3 chiffres était attendu. Nous avons également rencontré des problèmes où nous étions censés faire une comparaison insensible à la casse et qui ne l'ont pas fait, ce qui a entraîné des bogues.
À partir de cela, j'ai décidé d'arrêter de passer des chaînes et de créer une Airport
classe, qui a un seul constructeur qui prend et valide le code de l'aéroport.
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);
}
}
Cela a rendu notre code beaucoup plus facile à comprendre et nous avons simplifié nos vérifications d'égalité, nos utilisations de dictionnaire / ensemble. Nous savons maintenant que si nos méthodes acceptent une Airport
instance qui se comportera comme nous l'attendons, cela a simplifié nos vérifications de méthodes en une vérification de référence nulle.
La chose que j'ai remarquée, cependant, était que la collecte des ordures fonctionnait beaucoup plus souvent, ce que j'ai retrouvé dans de nombreux cas de Airport
collecte.
Ma solution à cela a été de convertir le fichier class
en a struct
. Il s'agissait principalement d'un changement de mot clé, à l'exception de GetHashCode
et ToString
:
public override string ToString()
{
return Code ?? string.Empty;
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
Pour gérer le cas où default(Airport)
est utilisé.
Mes questions:
La création d'une
Airport
classe ou d'une structure était-elle une bonne solution en général, ou suis-je en train de résoudre le mauvais problème / le résoudre de la mauvaise façon en créant le type? Si ce n'est pas une bonne solution, quelle est la meilleure solution?Comment mon application doit-elle gérer les instances où le
default(Airport)
est utilisé? Un type dedefault(Airport)
n'a pas de sens pour mon application, donc je l'ai faitif (airport == default(Airport) { throw ... }
dans des endroits où l'obtention d'une instance deAirport
(et de sesCode
propriétés) est essentielle à l'opération.
Notes: J'ai passé en revue les questions C # / VB struct - comment éviter la casse avec des valeurs par défaut nulles, qui est considérée comme invalide pour une structure donnée? et utilisez struct ou non avant de poser ma question, mais je pense que mes questions sont suffisamment différentes pour justifier son propre message.
default(Airport)
problème en interdisant simplement les instances par défaut. Vous pouvez le faire en écrivant un constructeur et de jeter parameterless InvalidOperationException
ou NotImplementedException
en elle.