Menganggap C # tidak bisa switch
di Tipe (yang saya kumpulkan tidak ditambahkan sebagai kasus khusus karena is
hubungan berarti bahwa lebih dari satu perbedaan case
mungkin berlaku), apakah ada cara yang lebih baik untuk mensimulasikan beralih pada tipe selain ini?
void Foo(object o)
{
if (o is A)
{
((A)o).Hop();
}
else if (o is B)
{
((B)o).Skip();
}
else
{
throw new ArgumentException("Unexpected type: " + o.GetType());
}
}
switch
pernyataan. Satu contoh: Perakitan A berisi sekumpulan objek data (yang tidak akan berubah, ditentukan dalam dokumen spesifikasi atau semacamnya). Rakitan B , C , dan D masing-masing referensi A dan memberikan konversi untuk berbagai objek data dari A (misalnya serialisasi / deserialisasi ke beberapa format tertentu). Anda harus mencerminkan seluruh hierarki kelas di B , C , dan D , dan menggunakan pabrik, atau Anda harus ...Jawaban:
Mengaktifkan jenis pasti kurang dalam C # ( UPDATE: di C # 7 / VS 2017 diaktifkan jenis didukung - lihat jawaban Zachary Yates di bawah ). Untuk melakukan ini tanpa pernyataan if / else if / else yang besar, Anda harus bekerja dengan struktur yang berbeda. Saya menulis posting blog sementara merinci bagaimana membangun struktur TypeSwitch.
https://docs.microsoft.com/archive/blogs/jaredpar/switching-on-types
Versi singkat: TypeSwitch dirancang untuk mencegah pengecoran redundan dan memberikan sintaksis yang mirip dengan pernyataan sakelar / kasus normal. Misalnya, di sini adalah TypeSwitch beraksi pada acara formulir Windows standar
Kode untuk TypeSwitch sebenarnya cukup kecil dan dapat dengan mudah dimasukkan ke dalam proyek Anda.
sumber
foreach
(yang hanya akan terjadi jika kecocokan tidak ditemukan)CaseInfo
dengan hanya memeriksa terhadap nilai jenis (jika nolnya adalah default).Dengan C # 7 , yang dikirimkan bersama Visual Studio 2017 (Rilis 15. *), Anda dapat menggunakan Jenis dalam
case
pernyataan (pencocokan pola):Dengan C # 6, Anda dapat menggunakan pernyataan switch dengan operator nameof () (terima kasih @ Joey Adams):
Dengan C # 5 dan sebelumnya, Anda bisa menggunakan pernyataan peralihan, tetapi Anda harus menggunakan string ajaib yang berisi nama jenis ... yang tidak terlalu ramah untuk refactor (terima kasih @nukefusion)
sumber
nameof()
operator baru yang mengkilap .case UnauthorizedException _:
Salah satu opsi adalah memiliki kamus dari
Type
keAction
(atau delegasi lain). Cari tindakan berdasarkan tipe, dan kemudian jalankan. Saya telah menggunakan ini untuk pabrik sebelumnya.sumber
Dengan jawaban JaredPar di belakang kepala saya, saya menulis varian
TypeSwitch
kelasnya yang menggunakan inferensi tipe untuk sintaksis yang lebih bagus:Perhatikan bahwa urutan
Case()
metode ini penting.Dapatkan kode lengkap dan komentar untuk
TypeSwitch
kelas saya . Ini adalah versi singkat yang berfungsi:sumber
public static Switch<TSource> Case<TSource, TTarget>(this TSource value, Action<TTarget> action) where TTarget : TSource
. Ini memungkinkan Anda mengatakanvalue.Case((C x) ...
Buat superclass (S) dan buat A dan B mewarisinya. Kemudian nyatakan sebuah metode abstrak pada S yang perlu diterapkan oleh setiap subclass.
Melakukan hal ini dengan metode "foo" juga dapat mengubah tanda tangannya menjadi Foo (S o), membuatnya aman, dan Anda tidak perlu membuang pengecualian jelek itu.
sumber
Anda dapat menggunakan pencocokan pola dalam C # 7 atau lebih tinggi:
sumber
Anda harus benar-benar membebani metode Anda, bukan mencoba melakukan disambiguasi sendiri. Sebagian besar jawaban sejauh ini tidak memperhitungkan subclass masa depan, yang dapat menyebabkan masalah pemeliharaan yang sangat mengerikan di kemudian hari.
sumber
Jika Anda menggunakan C # 4, Anda dapat menggunakan fungsionalitas dinamis baru untuk mencapai alternatif yang menarik. Saya tidak mengatakan ini lebih baik, bahkan sepertinya sangat lambat, tetapi memang memiliki keanggunan tertentu.
Dan penggunaannya:
Alasan ini berhasil adalah bahwa pemanggilan metode dinamis C # 4 memiliki kelebihannya diselesaikan pada saat runtime daripada waktu kompilasi. Saya menulis sedikit lebih banyak tentang ide ini baru-baru ini . Sekali lagi, saya hanya ingin menegaskan kembali bahwa ini mungkin berkinerja lebih buruk daripada semua saran lainnya, saya menawarkannya hanya sebagai rasa ingin tahu.
sumber
Ya, terima kasih kepada C # 7 yang dapat dicapai. Begini caranya (menggunakan pola ekspresi ):
sumber
Untuk tipe bawaan, Anda dapat menggunakan enumerasi TypeCode. Harap perhatikan bahwa GetType () agak lambat, tetapi mungkin tidak relevan di sebagian besar situasi.
Untuk tipe khusus, Anda dapat membuat enumerasi sendiri, dan antarmuka atau kelas dasar dengan properti atau metode abstrak ...
Implementasi kelas abstrak properti
Implementasi metode abstrak kelas
Implementasi antarmuka properti
Implementasi antarmuka metode
Salah satu rekan kerja saya baru saja memberi tahu saya tentang ini: Ini memiliki keuntungan bahwa Anda dapat menggunakannya untuk semua jenis objek, bukan hanya yang Anda tetapkan. Ini memiliki kelemahan menjadi sedikit lebih besar dan lebih lambat.
Pertama-tama tentukan kelas statis seperti ini:
Dan kemudian Anda dapat menggunakannya seperti ini:
sumber
Saya menyukai penggunaan pengetikan implisit Virtlink untuk membuat pergantian jauh lebih mudah dibaca, tetapi saya tidak suka bahwa permulaan tidak mungkin, dan kami sedang mengalokasikan. Mari kita naikkan perf sedikit.
Nah, itu membuat jari saya sakit. Mari kita lakukan di T4:
Menyesuaikan sedikit contoh Virtlink:
Dapat dibaca dan cepat. Sekarang, ketika semua orang terus menunjukkan jawaban mereka, dan mengingat sifat pertanyaan ini, urutan penting dalam jenis pencocokan. Karena itu:
sumber
Mengingat warisan memfasilitasi suatu objek untuk dikenali sebagai lebih dari satu jenis, saya pikir peralihan dapat menyebabkan ambiguitas buruk. Sebagai contoh:
Kasus 1
Kasus 2
Karena s adalah string dan objek. Saya pikir ketika Anda menulis
switch(foo)
Anda berharap foo mencocokkan satu dan hanya satu daricase
pernyataan tersebut. Dengan sakelar jenis, urutan di mana Anda menulis pernyataan kasus Anda mungkin dapat mengubah hasil seluruh pernyataan sakelar. Saya pikir itu salah.Anda bisa memikirkan kompiler-periksa pada jenis pernyataan "typeswitch", memeriksa bahwa jenis yang disebutkan tidak mewarisi satu sama lain. Itu tidak ada.
foo is T
tidak sama denganfoo.GetType() == typeof(T)
!!sumber
Saya juga akan
sumber
Cara lain adalah dengan mendefinisikan antarmuka IThing dan kemudian mengimplementasikannya di kedua kelas di sini snipet:
sumber
Sesuai spesifikasi C # 7.0, Anda dapat mendeklarasikan variabel lokal yang dicakup dalam
case
aswitch
:Ini adalah cara terbaik untuk melakukan hal seperti itu karena hanya melibatkan operasi casting dan push-on-the-stack, yang merupakan operasi tercepat yang dapat dijalankan oleh penerjemah tepat setelah operasi dan
boolean
kondisi bitwise .Membandingkan ini dengan
Dictionary<K, V>
, penggunaan memori di sini jauh lebih sedikit: memegang kamus membutuhkan lebih banyak ruang dalam RAM dan beberapa perhitungan lebih banyak oleh CPU untuk membuat dua array (satu untuk kunci dan lainnya untuk nilai) dan mengumpulkan kode hash untuk kunci untuk menempatkan nilai untuk kunci masing-masing.Jadi, sejauh yang saya tahu, saya tidak percaya bahwa cara yang lebih cepat bisa ada kecuali jika Anda ingin hanya menggunakan
if
-then
-else
blok denganis
operator sebagai berikut:sumber
Anda dapat membuat metode kelebihan beban:
Dan melemparkan argumen untuk
dynamic
mengetik untuk memotong memeriksa tipe statis:sumber
Penyempurnaan pencocokan pola C # 8 memungkinkan untuk melakukannya seperti ini. Dalam beberapa kasus itu melakukan pekerjaan dan lebih ringkas.
sumber
Anda sedang mencari
Discriminated Unions
yang merupakan fitur bahasa F #, tetapi Anda dapat mencapai efek yang sama dengan menggunakan perpustakaan yang saya buat, yang disebut OneOfhttps://github.com/mcintyre321/OneOf
Keuntungan utama atas
switch
(danif
danexceptions as control flow
) adalah bahwa ia aman saat kompilasi - tidak ada penangan default atau gagalJika Anda menambahkan item ketiga ke o, Anda akan mendapatkan kesalahan kompiler karena Anda harus menambahkan Fungsi handler di dalam panggilan sakelar.
Anda juga dapat melakukan hal
.Match
yang mengembalikan nilai, daripada mengeksekusi pernyataan:sumber
Buat antarmuka
IFooable
, lalu buat kelas AndaA
danB
untuk menerapkan metode umum, yang pada gilirannya memanggil metode yang sesuai yang Anda inginkan:Perhatikan, bahwa lebih baik menggunakan
as
alih-alih memeriksa terlebih dahuluis
dan kemudian casting, karena dengan begitu Anda membuat 2 gips, jadi lebih mahal.sumber
Saya seperti kasus saya biasanya berakhir dengan daftar predikat dan tindakan. Sesuatu di sepanjang garis ini:
sumber
Setelah membandingkan opsi beberapa jawaban di sini yang disediakan untuk fitur F #, saya menemukan F # memiliki cara dukungan yang lebih baik untuk switching berbasis tipe (walaupun saya masih berpegang teguh pada C #).
Anda mungkin ingin melihat di sini dan di sini .
sumber
Saya akan membuat antarmuka dengan nama dan metode apa pun yang masuk akal untuk peralihan Anda, sebut saja masing-masing:
IDoable
yang memberitahu untuk diimplementasikanvoid Do()
.dan ubah metode sebagai berikut:
Setidaknya dengan itu Anda aman pada saat kompilasi dan saya curiga bahwa kinerja lebih baik daripada memeriksa jenis saat runtime.
sumber
Dengan C # 8 dan seterusnya, Anda dapat membuatnya lebih ringkas dengan sakelar baru. Dan dengan menggunakan opsi buang _ Anda dapat menghindari membuat variabel innecesary ketika Anda tidak membutuhkannya, seperti ini:
Invoice dan ShippingList adalah kelas dan dokumen adalah objek yang dapat berupa salah satunya.
sumber
Saya setuju dengan Jon tentang hash tindakan untuk nama kelas. Jika Anda mempertahankan pola Anda, Anda mungkin ingin mempertimbangkan untuk menggunakan konstruk "sebagai":
Perbedaannya adalah ketika Anda menggunakan derai if (foo is Bar) {((Bar) foo) .Action (); } Anda melakukan casting tipe dua kali. Sekarang mungkin kompiler akan mengoptimalkan dan hanya melakukan itu berfungsi sekali - tetapi saya tidak akan mengandalkannya.
sumber
Seperti yang Pablo sarankan, pendekatan antarmuka hampir selalu merupakan hal yang benar untuk dilakukan untuk menangani hal ini. Untuk benar-benar menggunakan switch, alternatif lain adalah memiliki custom enum yang menunjukkan tipe Anda di kelas Anda.
Ini juga diimplementasikan di BCL. Salah satu contoh adalah MemberInfo.MemberTypes , yang lainnya adalah
GetTypeCode
untuk tipe primitif, seperti:sumber
Ini adalah jawaban alternatif yang menggabungkan kontribusi dari JaredPar dan VirtLink, dengan batasan-batasan berikut:
Pemakaian:
Kode:
sumber
Ya - cukup gunakan "pencocokan pola" yang agak aneh bernama dari C # 7 ke atas untuk mencocokkan pada kelas atau struktur:
sumber
saya menggunakan
sumber
Harus bekerja dengan
Suka:
sumber
Jika Anda tahu kelas yang Anda harapkan tetapi Anda masih tidak memiliki objek, Anda bahkan dapat melakukan ini:
sumber