In meinem System häufig mit Flughafen - Codes arbeiten I ( "YYZ"
, "LAX"
, "SFO"
, etc.), sind sie immer in dem exakt gleichen Format (3 Buchstaben, als Groß dargestellt). Das System verarbeitet in der Regel 25 bis 50 dieser (unterschiedlichen) Codes pro API-Anforderung, wobei insgesamt über tausend Zuweisungen vorgenommen werden. Sie werden durch viele Ebenen unserer Anwendung geleitet und häufig auf Gleichheit verglichen.
Wir haben angefangen, indem wir nur Strings herumgereicht haben, was ein bisschen gut funktioniert hat, aber wir haben schnell viele Programmierfehler bemerkt, indem wir irgendwo einen falschen Code eingegeben haben, wo der dreistellige Code erwartet wurde. Wir sind auch auf Probleme gestoßen, bei denen wir einen Vergleich durchführen sollten, bei dem die Groß- und Kleinschreibung nicht berücksichtigt wurde. Dies führte zu Fehlern.
Aus diesem Grund habe ich beschlossen, keine Zeichenfolgen mehr weiterzugeben und eine Airport
Klasse mit einem einzigen Konstruktor zu erstellen , der den Flughafencode aufnimmt und validiert.
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);
}
}
Dies machte unseren Code viel verständlicher und wir vereinfachten unsere Gleichheitsprüfungen, Wörterbuch- / Satzverwendungen. Wir wissen jetzt, dass, wenn unsere Methoden eine Airport
Instanz akzeptieren , die sich wie erwartet verhält, dies unsere Methodenprüfungen zu einer Nullreferenzprüfung vereinfacht hat.
Was mir jedoch aufgefallen ist, ist, dass die Speicherbereinigung viel häufiger ausgeführt wird, was ich auf viele Fälle zurückgeführt habe, in denen Airport
sie gesammelt wurde.
Meine Lösung hierfür war, die class
in eine umzuwandeln struct
. Meistens handelte es sich nur um eine Keyword-Änderung, mit Ausnahme von GetHashCode
und ToString
:
public override string ToString()
{
return Code ?? string.Empty;
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
Um den Fall zu behandeln, in dem default(Airport)
verwendet wird.
Meine Fragen:
War das Erstellen einer
Airport
Klasse oder Struktur im Allgemeinen eine gute Lösung, oder löse ich das falsche Problem / löse ich es auf die falsche Weise, indem ich den Typ erstelle? Wenn es keine gute Lösung ist, was ist eine bessere Lösung?Wie soll meine Anwendung mit Fällen umgehen, in denen die
default(Airport)
verwendet wird? Eine Art vondefault(Airport)
ist für meine Anwendung unsinnig, daher habe ichif (airport == default(Airport) { throw ... }
an Stellen gearbeitet, an denen das Abrufen einer Instanz vonAirport
(und ihrerCode
Eigenschaft) für den Vorgang von entscheidender Bedeutung ist.
Anmerkungen: Ich habe die Fragen zur C # / VB-Struktur überprüft . Und Verwenden Struktur oder nicht , bevor meine Frage zu stellen, aber ich denke , meine Fragen unterschiedlich genug sind , seinen eigenen Beitrag zu rechtfertigen.
quelle
default(Airport)
Problem zu lösen , besteht darin, Standardinstanzen einfach zu deaktivieren. Sie können dies tun, indem Sie einen parameterlosen Konstruktor schreiben und werfenInvalidOperationException
oder hineinNotImplementedException
.Antworten:
Update: Ich habe meine Antwort umgeschrieben, um einige falsche Annahmen zu C # -Strukturen sowie das OP zu berücksichtigen, das uns in Kommentaren darüber informiert, dass interne Zeichenfolgen verwendet werden.
Wenn Sie die in Ihr System eingehenden Daten steuern können, verwenden Sie eine Klasse, wie Sie sie in Ihrer Frage angegeben haben. Wenn jemand rennt
default(Airport)
, bekommt er einennull
Wert zurück. Stellen Sie sicher, dass Sie Ihre privateEquals
Methode so schreiben , dass sie false zurückgibt, wenn Sie null Airport-Objekte vergleichen, und lassen Sie dieNullReferenceException
s dann an einer anderen Stelle im Code fliegen.Wenn Sie jedoch Daten aus Quellen in das System übernehmen, die Sie nicht kontrollieren, müssen Sie nicht den gesamten Thread zum Absturz bringen. In diesem Fall ist eine Struktur ideal, denn die einfache Tatsache
default(Airport)
gibt Ihnen etwas anderes als einennull
Zeiger. Stellen Sie einen offensichtlichen Wert für "kein Wert" oder "Standardwert" ein, damit Sie etwas auf dem Bildschirm oder in einer Protokolldatei drucken können (z. B. "---"). Tatsächlich würde ich nur dascode
Private behalten und eineCode
Immobilie überhaupt nicht aussetzen - mich nur auf das Verhalten konzentrieren.Im schlimmsten Fall wird die Konvertierung
default(Airport)
in eine Zeichenfolge gedruckt"---"
und im Vergleich zu anderen gültigen Flughafencodes als falsch zurückgegeben. Jeder "Standard" -Flughafencode stimmt mit nichts überein, einschließlich anderer Standard-Flughafencodes.Ja, Strukturen sind Werte, die auf dem Stack zugewiesen sind, und alle Zeiger auf den Heap-Speicher negieren im Grunde die Leistungsvorteile von Strukturen. In diesem Fall hat der Standardwert einer Struktur jedoch eine Bedeutung und bietet dem Rest des einen zusätzlichen Aufzählungsschutz Anwendung.
Ich würde die Regeln deswegen hier ein wenig verbiegen.
Originalantwort (mit einigen sachlichen Fehlern)
Wenn Sie die in Ihr System eingehenden Daten steuern können, würde ich wie in den Kommentaren von Robert Harvey vorgeschlagen vorgehen: Erstellen Sie einen parameterlosen Konstruktor und lösen Sie eine Ausnahme aus, wenn er aufgerufen wird. Dies verhindert, dass ungültige Daten über in das System gelangen
default(Airport)
.Wenn Sie jedoch Daten aus Quellen in das System übernehmen, die Sie nicht kontrollieren, müssen Sie nicht den gesamten Thread zum Absturz bringen. In diesem Fall können Sie einen ungültigen Flughafencode erstellen, der jedoch als offensichtlicher Fehler erscheint. Dazu müsste ein parameterloser Konstruktor erstellt und der
Code
Wert auf "---" gesetzt werden:Da Sie a
string
als Code verwenden, hat die Verwendung einer Struktur keinen Sinn. Die Struktur wird auf dem Stapel zugewiesen, nur um denCode
als Zeiger auf eine Zeichenfolge im Heapspeicher zugewiesenen Wert zu haben. Daher besteht hier kein Unterschied zwischen Klasse und Struktur.Wenn Sie den Flughafencode in ein Array mit 3 Zeichen ändern, wird dem Stapel eine Struktur vollständig zugewiesen. Selbst dann ist das Datenvolumen nicht so groß, um einen Unterschied zu machen.
quelle
Code
Eigenschaft verwenden würde, würde dies Ihre Rechtfertigung in Bezug auf die Position der Zeichenfolge im Heapspeicher ändern?Verwenden Sie das Flyweight-Muster
Da der Flughafen zu Recht unveränderlich ist, ist es nicht erforderlich, mehr als eine Instanz einer bestimmten Instanz zu erstellen, z. B. SFO. Verwenden Sie eine Hashtabelle oder ähnliches (Anmerkung: Ich bin ein Java-Typ, nicht C #, daher können die genauen Details variieren), um Flughäfen beim Erstellen zwischenzuspeichern. Bevor Sie eine neue erstellen, checken Sie die Hashtabelle ein. Sie geben niemals Flughäfen frei, daher muss GC sie nicht freigeben.
Ein weiterer kleiner Vorteil (zumindest in Java, bei C # nicht sicher) ist, dass Sie keine
equals()
Methode schreiben müssen , ein einfacher==
Wille genügt. Gleiches gilt fürhashcode()
.quelle
getAirportOrCreate()
Code richtig synchronisiert ist, gibt es keinen technischen Grund, warum Sie zur Laufzeit keine neuen Flughäfen nach Bedarf erstellen können. Es kann geschäftliche Gründe geben.Ich bin kein besonders fortgeschrittener Programmierer, aber wäre das nicht eine perfekte Verwendung für ein Enum?
Es gibt verschiedene Möglichkeiten, Enum-Klassen aus Listen oder Zeichenfolgen zu erstellen. Hier ist eine, die ich in der Vergangenheit gesehen habe, nicht sicher, ob es der beste Weg ist.
https://blog.kloud.com.au/2016/06/17/converting-webconfig-values-into-enum-or-list/
quelle
Einer der Gründe, warum Sie mehr GC-Aktivitäten sehen, ist, dass Sie jetzt eine zweite Zeichenfolge erstellen - die
.ToUpperInvariant()
Version der ursprünglichen Zeichenfolge. Die ursprüngliche Zeichenfolge kann unmittelbar nach der Ausführung des Konstruktors für GC verwendet werden, und die zweite Zeichenfolge kann gleichzeitig mit demAirport
Objekt verwendet werden. Möglicherweise können Sie es auf andere Weise minimieren (beachten Sie den dritten Parameter fürstring.Equals()
):quelle
GetHashCode
sollte nur verwendenStringComparer.OrdinalIgnoreCase.GetHashCode(Code)
oder ähnlich