Dalam sistem saya saya sering beroperasi dengan kode bandara ( "YYZ"
, "LAX"
, "SFO"
, dll), mereka selalu dalam format yang sama persis (3 huruf, direpresentasikan sebagai huruf besar). Sistem ini biasanya menangani 25-50 kode (berbeda) ini per permintaan API, dengan lebih dari seribu total alokasi, mereka diedarkan melalui banyak lapisan aplikasi kita, dan cukup sering dibandingkan untuk kesetaraan.
Kami mulai dengan hanya memberikan string di sekitar, yang bekerja dengan baik untuk sedikit tetapi kami dengan cepat melihat banyak kesalahan pemrograman dengan mengirimkan kode yang salah di suatu tempat kode 3 digit diharapkan. Kami juga mengalami masalah di mana kami seharusnya melakukan perbandingan case-insensitive dan sebaliknya tidak, menghasilkan bug.
Dari sini, saya memutuskan untuk berhenti melewati string dan membuat Airport
kelas, yang memiliki konstruktor tunggal yang mengambil dan memvalidasi kode bandara.
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);
}
}
Ini membuat kode kami jauh lebih mudah untuk dipahami dan kami menyederhanakan pemeriksaan kesetaraan, penggunaan kamus / set kami. Kita sekarang tahu bahwa jika metode kita menerima Airport
contoh bahwa ia akan berperilaku seperti yang kita harapkan, itu telah menyederhanakan pemeriksaan metode kita menjadi pemeriksaan referensi nol.
Namun yang saya perhatikan adalah pengumpulan sampah berjalan lebih sering, yang saya telusuri hingga banyak contoh Airport
pengumpulan.
Solusi saya untuk ini adalah untuk mengubah class
menjadi struct
. Sebagian besar itu hanya perubahan kata kunci, dengan pengecualian GetHashCode
dan ToString
:
public override string ToString()
{
return Code ?? string.Empty;
}
public override int GetHashCode()
{
return Code?.GetHashCode() ?? 0;
}
Untuk menangani kasing di mana default(Airport)
digunakan.
Pertanyaan saya:
Apakah membuat
Airport
kelas atau struct merupakan solusi yang baik secara umum, atau apakah saya memecahkan masalah yang salah / menyelesaikannya dengan cara yang salah dengan membuat tipe? Jika itu bukan solusi yang baik, apa solusi yang lebih baik?Bagaimana seharusnya aplikasi saya menangani instance tempat
default(Airport)
digunakan? Jenisdefault(Airport)
tidak masuk akal untuk aplikasi saya, jadi saya sudah melakukanif (airport == default(Airport) { throw ... }
di tempat-tempat di mana mendapatkan instanceAirport
(danCode
propertinya) sangat penting untuk operasi.
Catatan: Saya meninjau pertanyaan C # / VB struct - bagaimana cara menghindari case dengan nol nilai default, yang dianggap tidak valid untuk struktur yang diberikan? , dan Gunakan struct atau tidak sebelum mengajukan pertanyaan saya, namun saya pikir pertanyaan saya cukup berbeda untuk menjamin posnya sendiri.
sumber
default(Airport)
masalah adalah dengan tidak mengizinkan instance default. Anda dapat melakukannya dengan menulis konstruktor tanpa parameter dan melemparkanInvalidOperationException
atauNotImplementedException
di dalamnya.Jawaban:
Pembaruan: Saya menulis ulang jawaban saya untuk mengatasi beberapa asumsi yang salah tentang C # struct, serta OP yang memberi tahu kami dalam komentar bahwa string yang diinternir sedang digunakan.
Jika Anda dapat mengontrol data yang masuk ke sistem Anda, gunakan kelas saat Anda memposting di pertanyaan Anda. Jika seseorang berlari
default(Airport)
mereka akan mendapatkannull
nilai kembali. Pastikan untuk menulisEquals
metode pribadi Anda untuk mengembalikan false setiap kali membandingkan objek Bandara nol, dan kemudian biarkanNullReferenceException
terbang di tempat lain dalam kode.Namun, jika Anda mengambil data ke dalam sistem dari sumber yang tidak Anda kontrol, Anda tidak perlu ingin membuat seluruh utas macet. Dalam hal ini struct ideal untuk fakta sederhana
default(Airport)
akan memberi Anda sesuatu selainnull
pointer. Buat nilai yang jelas untuk mewakili "tidak ada nilai" atau "nilai default" sehingga Anda memiliki sesuatu untuk dicetak di layar atau dalam file log (seperti "---" misalnya). Bahkan, saya hanya akan menjagacode
privasi dan tidak mengeksposCode
properti sama sekali - hanya fokus pada perilaku di sini.Skenario kasus yang lebih buruk mengkonversi
default(Airport)
ke string mencetak"---"
dan mengembalikan false jika dibandingkan dengan kode bandara lainnya yang valid. Kode bandara "default" tidak cocok dengan apa pun, termasuk kode bandara standar lainnya.Ya, struct dimaksudkan sebagai nilai yang dialokasikan pada stack, dan setiap pointer untuk menumpuk memori pada dasarnya meniadakan keuntungan kinerja struct, tetapi dalam hal ini nilai default dari sebuah struct memiliki makna dan memberikan beberapa resistensi peluru tambahan ke seluruh aplikasi.
Saya akan sedikit membengkokkan aturan di sini, karena itu.
Jawaban Asli (dengan beberapa kesalahan faktual)
Jika Anda dapat mengontrol data yang masuk ke sistem Anda, saya akan melakukan seperti yang disarankan Robert Harvey dalam komentar: Buat konstruktor tanpa parameter dan berikan pengecualian saat dipanggil. Ini mencegah data yang tidak valid memasuki sistem melalui
default(Airport)
.Namun, jika Anda mengambil data ke dalam sistem dari sumber yang tidak Anda kontrol, Anda tidak perlu ingin membuat seluruh utas macet. Dalam hal ini Anda dapat membuat kode bandara yang tidak valid, tetapi membuatnya tampak seperti kesalahan nyata. Ini akan melibatkan pembuatan konstruktor tanpa parameter dan pengaturan
Code
ke sesuatu seperti "---":Karena Anda menggunakan
string
sebagai Kode, tidak ada gunanya menggunakan struct. Struct akan dialokasikan pada stack, hanya untukCode
dialokasikan sebagai pointer ke string di memori tumpukan, jadi tidak ada perbedaan di sini antara kelas dan struct.Jika Anda mengubah kode bandara menjadi array 3 item dari char maka struct akan sepenuhnya dialokasikan pada stack. Meski begitu volume data tidak terlalu besar untuk membuat perbedaan.
sumber
Code
properti, apakah itu akan mengubah pembenaran Anda tentang titik string yang ada di memori tumpukan?Gunakan pola Flyweight
Karena Airport, dengan benar, tidak dapat diubah, tidak perlu membuat lebih dari satu instance dari yang tertentu, katakanlah, SFO. Gunakan Hashtable atau yang serupa (perhatikan, saya seorang pria Java, bukan C # jadi detail yang tepat mungkin berbeda), untuk men-cache Bandara ketika mereka dibuat. Sebelum membuat yang baru, periksa di Hashtable. Anda tidak pernah membebaskan Bandara, jadi GC tidak perlu membebaskan mereka.
Satu keuntungan kecil tambahan (setidaknya di Jawa, tidak yakin tentang C #) adalah bahwa Anda tidak perlu menulis
equals()
metode, yang sederhana==
akan dilakukan. Sama untukhashcode()
.sumber
getAirportOrCreate()
kode disinkronkan dengan benar, tidak ada alasan teknis Anda tidak dapat membuat Bandara baru yang diperlukan selama runtime. Mungkin ada alasan bisnis.Saya bukan programmer yang sangat canggih, tetapi bukankah ini akan menjadi penggunaan yang sempurna untuk Enum?
Ada berbagai cara untuk membangun kelas enum dari daftar atau string. Ini salah satu yang pernah saya lihat di masa lalu, tidak yakin apakah itu cara terbaik.
https://blog.kloud.com.au/2016/06/17/converting-webconfig-values-into-enum-or-list/
sumber
Salah satu alasan Anda melihat lebih banyak aktivitas GC adalah karena Anda membuat string kedua sekarang -
.ToUpperInvariant()
versi dari string asli. String asli memenuhi syarat untuk GC tepat setelah konstruktor berjalan dan yang kedua memenuhi syarat pada saat yang sama denganAirport
objek. Anda mungkin dapat menguranginya dengan cara yang berbeda (perhatikan parameter ketiga untukstring.Equals()
):sumber
GetHashCode
, sebaiknya hanya menggunakanStringComparer.OrdinalIgnoreCase.GetHashCode(Code)
atau serupa