En mi sistema que con frecuencia operan con códigos de aeropuertos ( "YYZ"
, "LAX"
, "SFO"
, etc.), que siempre están en el mismo formato exacto (de 3 letras, representada en mayúsculas). El sistema generalmente maneja entre 25 y 50 de estos (diferentes) códigos por solicitud de API, con más de mil asignaciones en total, se pasan a través de muchas capas de nuestra aplicación y se comparan por la igualdad con bastante frecuencia.
Comenzamos simplemente pasando cadenas, lo que funcionó bien por un tiempo, pero rápidamente notamos muchos errores de programación al pasar un código incorrecto en algún lugar donde se esperaba el código de 3 dígitos. También nos encontramos con problemas en los que se suponía que debíamos hacer una comparación entre mayúsculas y minúsculas y, en cambio, no lo hicimos, lo que resultó en errores.
A partir de esto, decidí dejar de pasar cadenas y crear una Airport
clase, que tiene un solo constructor que toma y valida el código del aeropuerto.
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);
}
}
Esto hizo que nuestro código fuera mucho más fácil de entender y simplificamos nuestros controles de igualdad, uso de diccionario / conjunto. Ahora sabemos que si nuestros métodos aceptan una Airport
instancia que se comportará de la manera que esperamos, ha simplificado nuestras verificaciones de métodos a una verificación de referencia nula.
Sin embargo, lo que sí noté fue que la recolección de basura se ejecutaba con mucha más frecuencia, lo que rastreé hasta muchos casos de recolección Airport
.
Mi solución a esto fue convertir el class
a struct
. Principalmente fue solo un cambio de palabra clave, con la excepción de GetHashCode
y ToString
:
public override string ToString()
{
return Code ?? string.Empty;
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
Para manejar el caso donde default(Airport)
se usa.
Mis preguntas:
¿Crear una
Airport
clase o estructura era una buena solución en general, o estoy resolviendo el problema incorrecto / resolviéndolo de manera incorrecta creando el tipo? Si no es una buena solución, ¿cuál es una mejor solución?¿Cómo debe manejar mi aplicación las instancias donde
default(Airport)
se usa? Un tipo de nodefault(Airport)
tiene sentido para mi aplicación, por lo que he estado haciendoif (airport == default(Airport) { throw ... }
en lugares donde obtener una instancia deAirport
(y suCode
propiedad) es fundamental para la operación.
Notas: Revisé las preguntas C # / VB struct: ¿cómo evitar el caso con valores predeterminados cero, que se considera inválido para la estructura dada? , y use struct o no antes de hacer mi pregunta, sin embargo, creo que mis preguntas son lo suficientemente diferentes como para justificar su propia publicación.
default(Airport)
problema es simplemente deshabilitando las instancias predeterminadas. Puede hacerlo escribiendo un constructor sin parámetros y lanzándolo InvalidOperationException
o NotImplementedException
en él.