W moim systemie często działają z kodami Airport ( "YYZ"
, "LAX"
, "SFO"
, itp), są one zawsze dokładnie w tym samym formacie (3 litery, reprezentowane jako wielkie litery). System zazwyczaj zajmuje się 25-50 z tych (różnych) kodów na każde żądanie API, z sumą ponad tysiąca alokacji, są one przekazywane przez wiele warstw naszej aplikacji i dość często porównywane.
Zaczęliśmy od przekazywania ciągów znaków, co działało trochę dobrze, ale szybko zauważyliśmy wiele błędów programistycznych, przekazując niewłaściwy kod, gdzie oczekiwano 3-cyfrowego kodu. Natknęliśmy się również na problemy, w których mieliśmy wykonać porównanie bez rozróżniania wielkości liter, a zamiast tego nie wystąpiły błędy, co spowodowało błędy.
Z tego postanowiłem przestać przekazywać ciągi znaków i stworzyć Airport
klasę, która ma jednego konstruktora, który pobiera i sprawdza kod lotniska.
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);
}
}
Dzięki temu nasz kod był znacznie łatwiejszy do zrozumienia i uprościliśmy nasze kontrole równości, słownik / zestaw zastosowań. Wiemy teraz, że jeśli nasze metody zaakceptują Airport
wystąpienie, które będzie zachowywać się tak, jak się spodziewamy, uprościło nasze sprawdzanie metod do zerowego sprawdzania referencji.
Zauważyłem jednak, że zbieranie śmieci działało znacznie częściej, co prześledziłem do wielu przypadków Airport
zbierania.
Moim rozwiązaniem tego było przekonwertowanie class
pliku na struct
. Przeważnie była to tylko zmiana słowa kluczowego, z wyjątkiem GetHashCode
i ToString
:
public override string ToString()
{
return Code ?? string.Empty;
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
Do obsługi przypadku, w którym default(Airport)
jest używany.
Moje pytania:
Czy tworzenie
Airport
klasy lub struktury było ogólnie dobrym rozwiązaniem, czy też rozwiązuję niewłaściwy problem / rozwiązuję go w niewłaściwy sposób, tworząc typ? Jeśli nie jest to dobre rozwiązanie, jakie jest lepsze rozwiązanie?Jak moja aplikacja powinna obsługiwać instancje, w których
default(Airport)
jest używana? Rodzaj aplikacjidefault(Airport)
jest nonsensowny dla mojej aplikacji, więc robiłem toif (airport == default(Airport) { throw ... }
w miejscach, w których uzyskanie wystąpieniaAirport
(i jegoCode
właściwości) ma kluczowe znaczenie dla operacji.
Uwagi: Przejrzałem pytania dotyczące struktury C # / VB - jak uniknąć przypadku z zerowymi wartościami domyślnymi, które są uważane za nieprawidłowe dla danej struktury? , i użyj struct lub nie przed zadaniem pytania, jednak myślę, że moje pytania są na tyle inne, że uzasadniają swój post.
default(Airport)
problemu jest po prostu niedozwolone wystąpienia domyślne. Możesz to zrobić, pisząc konstruktor bez parametrów i rzucając go InvalidOperationException
lub NotImplementedException
w nim.