C # mengalihkan batasan pernyataan - mengapa?

140

Saat menulis pernyataan beralih, tampaknya ada dua batasan pada apa yang dapat Anda aktifkan dalam pernyataan kasus.

Misalnya (dan ya, saya tahu, jika Anda melakukan hal semacam ini, itu mungkin berarti arsitektur berorientasi objek (OO) Anda rapuh - ini hanya contoh yang dibuat-buat!),

  Type t = typeof(int);

  switch (t) {

    case typeof(int):
      Console.WriteLine("int!");
      break;

    case typeof(string):
      Console.WriteLine("string!");
      break;

    default:
      Console.WriteLine("unknown!");
      break;
  }

Di sini pernyataan switch () gagal dengan 'Nilai dari tipe integral yang diharapkan' dan pernyataan kasus gagal dengan 'Nilai konstan diharapkan'.

Mengapa pembatasan ini diberlakukan, dan apa justifikasi yang mendasarinya? Saya tidak melihat alasan mengapa pernyataan switch memiliki untuk menyerah untuk analisis statis saja, dan mengapa nilai makhluk diaktifkan harus integral (yaitu, primitif). Apa pembenarannya?

ls
sumber
1
Lihat kemungkinan penyelesaiannya Apakah ada alternatif yang lebih baik dari ini untuk 'mengaktifkan tipe'?
Michael Freidgeim
Opsi lain untuk mengaktifkan tipe bawaan adalah menggunakan TypeCode Enum .
Erik Philips

Jawaban:

98

Ini adalah posting asli saya, yang memicu beberapa perdebatan ... karena itu salah :

Pernyataan peralihan tidak sama dengan pernyataan besar jika ada. Setiap kasing harus unik dan dievaluasi secara statis. Pernyataan beralih melakukan cabang waktu konstan terlepas dari berapa banyak kasus yang Anda miliki. Pernyataan if-else mengevaluasi setiap kondisi hingga menemukan yang benar.


Faktanya, pernyataan sakelar C # tidak selalu merupakan cabang waktu yang konstan.

Dalam beberapa kasus kompiler akan menggunakan pernyataan switch CIL yang memang merupakan cabang waktu konstan menggunakan tabel lompatan. Namun, dalam kasus yang jarang seperti yang ditunjukkan oleh Ivan Hamilton kompiler dapat menghasilkan sesuatu yang sama sekali berbeda.

Ini sebenarnya cukup mudah untuk diverifikasi dengan menulis berbagai pernyataan C # switch, beberapa jarang, beberapa padat, dan melihat CIL yang dihasilkan dengan alat ildasm.exe.

Brian Ensink
sumber
4
Seperti disebutkan dalam jawaban lain (termasuk saya), klaim yang dibuat dalam jawaban ini tidak benar. Saya akan merekomendasikan penghapusan (jika hanya untuk menghindari menegakkan kesalahpahaman (mungkin umum) ini).
mweerden
Silakan lihat posting saya di bawah ini di mana saya menunjukkan, menurut pendapat saya secara meyakinkan, bahwa pernyataan beralih melakukan cabang waktu yang konstan.
Brian Ensink
Terima kasih banyak atas balasan Anda, Brian. Silakan lihat balasan Ivan Hamilton ((48259) [ beta.stackoverflow.com/questions/44905/#48259] ). Singkatnya: Anda berbicara tentang switch instruksi (dari CIL) yang tidak sama dengan switchpernyataan C #.
mweerden
Saya tidak percaya kompiler menghasilkan percabangan waktu-konstan ketika menyalakan string.
Drew Noakes
Apakah ini masih berlaku dengan pencocokan pola dalam pernyataan kasus sakelar di C # 7.0?
B. Darren Olson
114

Penting untuk tidak membingungkan pernyataan sakelar C # dengan instruksi sakelar CIL.

Switch CIL adalah tabel lompatan, yang membutuhkan indeks ke dalam set alamat lompat.

Ini hanya berguna jika kasus C # switch berdekatan:

case 3: blah; break;
case 4: blah; break;
case 5: blah; break;

Tapi sedikit gunanya jika tidak:

case 10: blah; break;
case 200: blah; break;
case 3000: blah; break;

(Anda membutuhkan tabel ~ 3000 entri dalam ukuran, dengan hanya 3 slot yang digunakan)

Dengan ekspresi yang tidak berdekatan, kompiler dapat mulai melakukan pemeriksaan linier if-else-if-else.

Dengan set ekspresi non-berdekatan yang lebih besar, kompiler dapat mulai dengan pencarian pohon biner, dan akhirnya jika-selain-jika-lain beberapa item terakhir.

Dengan set ekspresi yang berisi rumpun item yang berdekatan, kompiler dapat mencari pohon biner, dan akhirnya beralih CIL.

Ini penuh dengan "mays" & "mights", dan itu tergantung pada kompiler (mungkin berbeda dengan Mono atau Rotor).

Saya mereplikasi hasil Anda di mesin saya menggunakan case yang berdekatan:

total waktu untuk mengeksekusi sakelar 10 arah, 10000 iterasi (ms): 25.1383
perkiraan waktu per sakelar 10 arah (ms): 0.00251383

total waktu untuk mengeksekusi switch 50 arah, 10000 iterations (ms): 26,593
perkiraan waktu per switch 50 arah (ms): 0,0026593

total waktu untuk mengeksekusi saklar 5000 arah, 10000 iterasi (ms): 23,7094
perkiraan waktu per saklar 5000 cara (ms): 0,00237094

total waktu untuk mengeksekusi switch cara 50000, 10000 iterations (ms): 20.0933
perkiraan waktu per switch cara 50000 (ms): 0.00200933

Kemudian saya juga menggunakan ekspresi kasus yang tidak berdekatan:

total waktu untuk mengeksekusi saklar 10 arah, 10000 iterations (ms): 19.6189
waktu perkiraan per saklar 10 arah (ms): 0.00196189

total waktu untuk mengeksekusi switch 500 arah, 10000 iterations (ms): 19.1664
perkiraan waktu per switch 500 arah (ms): 0.00191664

total waktu untuk mengeksekusi saklar 5000 arah, 10000 iterasi (ms): 19,5871
perkiraan waktu per saklar 5000 cara (ms): 0,00195871

Pernyataan pergantian kasus 50.000 yang tidak berbatasan tidak akan dikompilasi.
"Ekspresi terlalu panjang atau rumit untuk dikompilasi di dekat 'ConsoleApplication1.Program.Main (string [])'

Yang lucu di sini, adalah bahwa pencarian pohon biner muncul sedikit (mungkin tidak secara statistik) lebih cepat daripada instruksi sakelar CIL.

Brian, Anda telah menggunakan kata " konstan ", yang memiliki makna yang sangat pasti dari perspektif teori kompleksitas komputasi. Sementara contoh integer berdekatan sederhana dapat menghasilkan CIL yang dianggap O (1) (konstan), contoh jarang adalah O (log n) (logaritmik), contoh-contoh yang dikelompokkan terletak di suatu tempat di antara, dan contoh kecil adalah O (n) (linear) ).

Ini bahkan tidak membahas situasi String, di mana statis Generic.Dictionary<string,int32>dapat dibuat, dan akan mengalami overhead yang pasti pada penggunaan pertama. Kinerja di sini akan tergantung pada kinerja Generic.Dictionary.

Jika Anda memeriksa Spesifikasi Bahasa C # (bukan spesifikasi CIL), Anda akan menemukan "15.7.2 Pernyataan sakelar" tidak menyebutkan "waktu konstan" atau bahwa implementasi yang mendasarinya bahkan menggunakan instruksi sakelar CIL (berhati-hati dalam mengasumsikan hal-hal seperti).

Pada akhirnya, saklar C # terhadap ekspresi integer pada sistem modern adalah operasi sub-mikrodetik, dan biasanya tidak perlu dikhawatirkan.


Tentu saja waktu ini akan tergantung pada mesin dan kondisi. Saya tidak akan memperhatikan tes waktu ini, durasi mikrodetik yang sedang kita bicarakan dikerdilkan oleh kode "nyata" yang sedang dijalankan (dan Anda harus menyertakan beberapa "kode nyata" jika tidak, kompiler akan mengoptimalkan cabang jauh), atau jitter dalam sistem. Jawaban saya didasarkan pada penggunaan IL DASM untuk memeriksa CIL yang dibuat oleh kompiler C #. Tentu saja, ini belum final, karena instruksi aktual yang dijalankan CPU kemudian dibuat oleh JIT.

Saya telah memeriksa instruksi CPU terakhir yang benar-benar dijalankan pada mesin x86 saya, dan dapat mengonfirmasi set switch sederhana yang berdekatan melakukan sesuatu seperti:

  jmp     ds:300025F0[eax*4]

Di mana pencarian pohon biner penuh dengan:

  cmp     ebx, 79Eh
  jg      3000352B
  cmp     ebx, 654h
  jg      300032BB
  
  cmp     ebx, 0F82h
  jz      30005EEE
Ivan Hamilton
sumber
Hasil percobaan Anda sedikit mengejutkan saya. Apakah Anda menukar milik Anda dengan Brian? Hasilnya memang menunjukkan peningkatan dengan ukuran sedangkan Anda tidak. Saya kehilangan sesuatu? Bagaimanapun, terima kasih atas balasan yang jelas.
mweerden
Sulit untuk menghitung waktu secara akurat dengan operasi sekecil itu. Kami tidak membagikan kode, atau prosedur pengujian. Saya tidak bisa melihat mengapa waktunya harus meningkat untuk kasus yang berdekatan. Milik saya 10x lebih cepat, sehingga lingkungan & kode pengujian dapat sangat bervariasi.
Ivan Hamilton
23

Alasan pertama yang terlintas dalam pikiran adalah historis :

Karena sebagian besar programmer C, C ++, dan Java tidak terbiasa memiliki kebebasan seperti itu, mereka tidak menuntutnya.

Alasan lain yang lebih valid adalah kompleksitas bahasa akan meningkat :

Pertama-tama, haruskah objek dibandingkan dengan .Equals()atau dengan ==operator? Keduanya berlaku dalam beberapa kasus. Haruskah kita memperkenalkan sintaks baru untuk melakukan ini? Haruskah kita membiarkan programmer memperkenalkan metode perbandingan mereka sendiri?

Selain itu, memungkinkan untuk mengaktifkan objek akan merusak asumsi mendasar tentang pernyataan switch . Ada dua aturan yang mengatur pernyataan sakelar bahwa kompiler tidak akan dapat menegakkan jika objek diizinkan untuk diaktifkan (lihat spesifikasi bahasa C # versi 3.0 , §8.7.2):

  • Bahwa nilai-nilai label sakelar adalah konstan
  • Bahwa nilai-nilai label sakelar berbeda (sehingga hanya satu blok sakelar dapat dipilih untuk ekspresi sakelar yang diberikan)

Pertimbangkan contoh kode ini dalam kasus hipotetis bahwa nilai kasus non-konstan diizinkan:

void DoIt()
{
    String foo = "bar";
    Switch(foo, foo);
}

void Switch(String val1, String val2)
{
    switch ("bar")
    {
        // The compiler will not know that val1 and val2 are not distinct
        case val1:
            // Is this case block selected?
            break;
        case val2:
            // Or this one?
            break;
        case "bar":
            // Or perhaps this one?
            break;
    }
}

Apa yang akan dilakukan kode? Bagaimana jika pernyataan kasus disusun ulang? Memang, salah satu alasan mengapa C # membuat switch jatuh secara ilegal adalah bahwa pernyataan switch dapat diatur ulang secara sewenang-wenang.

Aturan-aturan ini ada karena suatu alasan - sehingga programmer dapat, dengan melihat satu blok kasus, mengetahui dengan pasti kondisi tepat di mana blok dimasukkan. Ketika pernyataan switch yang disebutkan di atas tumbuh menjadi 100 baris atau lebih (dan itu akan), pengetahuan seperti itu sangat berharga.

Antti Kissaniemi
sumber
2
Dari catatan pada saklar pemesanan ulang. Fall through legal jika kasing tidak mengandung kode. Seperti, Kasus 1: Kasus 2: Console.WriteLine ("Hai"); istirahat;
Joel McBeth
10

Omong-omong, VB, yang memiliki arsitektur dasar yang sama, memungkinkan Select Casepernyataan yang jauh lebih fleksibel (kode di atas akan bekerja di VB) dan masih menghasilkan kode yang efisien di mana hal ini dimungkinkan sehingga argumen oleh kendala teknis harus dipertimbangkan dengan hati-hati.

Konrad Rudolph
sumber
1
The Select Caseen VB sangat fleksibel dan menghemat waktu super. Saya sangat merindukannya.
Eduardo Molteni
@EduardoMolteni Beralih ke F # lalu. Itu membuat switch Pascal dan VB tampak seperti anak-anak idiot dibandingkan.
Luaan
10

Sebagian besar, pembatasan itu diberlakukan karena perancang bahasa. Justifikasi yang mendasarinya mungkin kompatibilitas dengan sejarah bahasa, cita-cita, atau penyederhanaan desain kompiler.

Kompiler dapat (dan memang) memilih untuk:

  • buat pernyataan if-else besar
  • gunakan instruksi saklar MSIL (tabel lompat)
  • membangun Generic.Dictionary <string, int32>, mengisinya saat pertama kali digunakan, dan memanggil Generic.Dictionary <> :: TryGetValue () untuk indeks yang akan diteruskan ke instruksi pengalih MSIL (tabel langsung)
  • gunakan kombinasi if-elses & MSIL "switch" jumps

Pernyataan pergantian BUKAN cabang waktu yang konstan. Compiler mungkin menemukan jalan pintas (menggunakan hash bucket, dll), tetapi case yang lebih rumit akan menghasilkan kode MSIL yang lebih rumit dengan beberapa case bercabang lebih awal dari yang lain.

Untuk menangani case String, kompiler akan berakhir (pada titik tertentu) menggunakan a.Equals (b) (dan mungkin a.GetHashCode ()). Saya pikir itu akan menjadi trival bagi kompiler untuk menggunakan objek apa pun yang memenuhi kendala ini.

Adapun kebutuhan untuk ekspresi kasus statis ... beberapa dari optimasi tersebut (hashing, caching, dll) tidak akan tersedia jika ekspresi case tidak deterministik. Tetapi kita telah melihat bahwa kadang-kadang kompiler hanya memilih jalan if-else-if-else yang sederhana ...

Sunting: lomaxx - Pemahaman Anda tentang operator "typeof" tidak benar. Operator "typeof" digunakan untuk mendapatkan objek System.Type untuk suatu tipe (tidak ada hubungannya dengan supertype atau interface-nya). Memeriksa kompatibilitas run-time dari suatu objek dengan tipe yang diberikan adalah pekerjaan operator "is". Penggunaan "typeof" di sini untuk mengekspresikan suatu objek tidak relevan.

Ivan Hamilton
sumber
6

Sementara pada topik, menurut Jeff Atwood, pernyataan switch adalah kekejaman pemrograman . Gunakan dengan hemat.

Anda sering dapat menyelesaikan tugas yang sama menggunakan tabel. Sebagai contoh:

var table = new Dictionary<Type, string>()
{
   { typeof(int), "it's an int!" }
   { typeof(string), "it's a string!" }
};

Type someType = typeof(int);
Console.WriteLine(table[someType]);
Yehuda Gabriel Himango
sumber
7
Anda serius mengutip seseorang dari kiriman Twitter tanpa bukti? Setidaknya tautan ke sumber yang dapat diandalkan.
Ivan Hamilton
4
Itu dari sumber yang dapat dipercaya; posting Twitter yang dimaksud adalah dari Jeff Atwood, penulis situs yang Anda cari. :-) Jeff memiliki beberapa posting blog tentang topik ini jika Anda penasaran.
Yehuda Gabriel Himango
Saya percaya itu adalah BS total - baik Jeff Atwood yang menulisnya atau tidak. Sangat lucu seberapa baik pernyataan switch cocok untuk menangani mesin negara, dan contoh lain dari mengubah aliran kode berdasarkan nilai suatu enumtipe. Ini juga bukan kebetulan bahwa Intellisense secara otomatis mengisi pernyataan beralih ketika Anda mengaktifkan variabel enumjenis.
Jonathon Reinhart
@ JonathonReinhart Ya, saya pikir itu intinya - ada cara yang lebih baik untuk menangani kode polimorfik daripada menggunakan switchpernyataan. Dia tidak mengatakan Anda tidak boleh menulis mesin negara, hanya saja Anda dapat melakukan hal yang sama dengan menggunakan tipe spesifik yang bagus. Tentu saja, ini jauh lebih mudah dalam bahasa seperti F # yang memiliki jenis yang dapat dengan mudah mencakup negara yang cukup kompleks. Sebagai contoh Anda, Anda bisa menggunakan serikat terdiskriminasi di mana negara menjadi bagian dari jenis, dan ganti switchdengan pencocokan pola. Atau gunakan antarmuka, misalnya.
Luaan
Jawaban lama / pertanyaan, tetapi saya akan berpikir bahwa (perbaiki saya jika saya salah) Dictionaryakan jauh lebih lambat daripada switchpernyataan yang dioptimalkan ...?
Paul
6

Saya tidak melihat alasan mengapa pernyataan switch harus mendukung analisis statis saja

Benar, itu tidak harus , dan banyak bahasa sebenarnya menggunakan pernyataan peralihan dinamis. Namun ini berarti bahwa penataan ulang klausa "case" dapat mengubah perilaku kode.

Ada beberapa info menarik di balik keputusan desain yang masuk ke "sakelar" di sini: Mengapa pernyataan sakelar C # dirancang untuk tidak mengizinkan proses jatuh, tetapi masih membutuhkan istirahat?

Mengizinkan ekspresi case dinamis dapat menyebabkan monstrositas seperti kode PHP ini:

switch (true) {
    case a == 5:
        ...
        break;
    case b == 10:
        ...
        break;
}

yang sejujurnya hanya menggunakan if-elsepernyataan itu.

Roman Starkov
sumber
1
Itulah yang saya sukai tentang PHP (sekarang karena saya sedang transisi ke C #), ini adalah kebebasan. Dengan itu muncul kebebasan untuk menulis kode yang buruk, tetapi ini adalah sesuatu yang saya sangat rindukan di C #
silkfire
5

Microsoft akhirnya mendengarmu!

Sekarang dengan C # 7 Anda dapat:

switch(shape)
{
case Circle c:
    WriteLine($"circle with radius {c.Radius}");
    break;
case Rectangle s when (s.Length == s.Height):
    WriteLine($"{s.Length} x {s.Height} square");
    break;
case Rectangle r:
    WriteLine($"{r.Length} x {r.Height} rectangle");
    break;
default:
    WriteLine("<unknown shape>");
    break;
case null:
    throw new ArgumentNullException(nameof(shape));
}
dimaaan
sumber
3

Ini bukan alasan mengapa, tetapi bagian spesifikasi C # 8.7.2 menyatakan sebagai berikut:

Tipe pemerintahan dari pernyataan switch ditentukan oleh ekspresi switch. Jika tipe ekspresi switch adalah sbyte, byte, short, ushort, int, uint, long, ulong, char, string, atau enum-type, maka itu adalah tipe yang mengatur pernyataan switch. Jika tidak, tepat satu konversi implisit yang ditentukan pengguna (§6.4) harus ada dari jenis ekspresi sakelar ke salah satu dari kemungkinan jenis pemerintahan berikut: sbyte, byte, pendek, ushort, int, uint, panjang, ulong, char, string . Jika tidak ada konversi tersirat seperti itu, atau jika lebih dari satu konversi tersirat tersebut ada, kesalahan waktu kompilasi terjadi.

Spesifikasi C # 3.0 terletak di: http://download.microsoft.com/download/3/8/8/388e7205-bc10-4226-b2a8-75351c669b09/CSharp%20Language%20Specification.doc

markus
sumber
3

Jawaban Yehuda di atas memberi saya ide. Anda dapat "memalsukan" perilaku sakelar OP di atas menggunakan Dictionary<Type, Func<T>:

Dictionary<Type, Func<object, string,  string>> typeTable = new Dictionary<Type, Func<object, string, string>>();
typeTable.Add(typeof(int), (o, s) =>
                    {
                        return string.Format("{0}: {1}", s, o.ToString());
                    });

Ini memungkinkan Anda untuk mengaitkan perilaku dengan tipe dengan gaya yang sama dengan pernyataan switch. Saya percaya ini memiliki manfaat tambahan sebagai kunci daripada tabel lompatan beralih-gaya ketika dikompilasi ke IL.

Dave Swersky
sumber
0

Saya kira tidak ada alasan mendasar mengapa kompiler tidak dapat secara otomatis menerjemahkan pernyataan switch Anda menjadi:

if (t == typeof(int))
{
...
}
elseif (t == typeof(string))
{
...
}
...

Tapi tidak banyak yang didapat dari itu.

Pernyataan kasus tentang tipe integral memungkinkan kompiler membuat sejumlah optimasi:

  1. Tidak ada duplikasi (kecuali Anda menduplikasi label kasus, yang terdeteksi oleh kompiler). Dalam contoh Anda t bisa cocok dengan beberapa jenis karena warisan. Haruskah pertandingan pertama dieksekusi? Mereka semua?

  2. Compiler dapat memilih untuk mengimplementasikan pernyataan switch pada tipe integral dengan tabel lompatan untuk menghindari semua perbandingan. Jika Anda mengaktifkan enumerasi yang memiliki nilai integer 0 hingga 100, maka ia menciptakan sebuah array dengan 100 pointer di dalamnya, satu untuk setiap pernyataan switch. Saat runtime hanya mencari alamat dari array berdasarkan nilai integer yang diaktifkan. Ini membuat kinerja runtime yang jauh lebih baik daripada melakukan 100 perbandingan.

Rob Walker
sumber
1
Kompleksitas penting yang perlu diperhatikan di sini adalah bahwa model .NET memory memiliki jaminan kuat tertentu yang membuat pseudocode Anda tidak persis setara dengan (hipotetis, tidak valid C # ) switch (t) { case typeof(int): ... }karena terjemahan Anda menyiratkan bahwa variabel t harus diambil dari memori dua kali jika t != typeof(int), sedangkan yang terakhir akan (Putatif) selalu membaca nilai t tepat sekali . Perbedaan ini dapat merusak kebenaran kode konkuren yang mengandalkan jaminan yang sangat baik. Untuk info lebih lanjut tentang ini, lihat Pemrograman Serentak
Glenn Slayden
0

Menurut dokumentasi pernyataan sakelar jika ada cara jelas untuk secara implisit mengkonversi objek ke tipe integral, maka itu akan diizinkan. Saya pikir Anda mengharapkan perilaku di mana untuk setiap pernyataan kasus itu akan diganti if (t == typeof(int)), tapi itu akan membuka seluruh kaleng cacing ketika Anda bisa membebani operator itu. Perilaku ini akan berubah ketika detail implementasi untuk pernyataan sakelar berubah jika Anda salah menuliskan == Anda. Dengan mengurangi perbandingan untuk tipe dan string yang tidak terpisahkan dan hal-hal yang dapat direduksi menjadi tipe integral (dan dimaksudkan untuk) mereka menghindari masalah potensial.

fryguybob
sumber
0

menulis:

"Pernyataan peralihan melakukan percabangan waktu yang konstan terlepas dari berapa banyak kasus yang kamu miliki."

Karena bahasa ini memungkinkan tipe string untuk digunakan dalam pernyataan switch saya kira kompiler tidak dapat menghasilkan kode untuk implementasi cabang waktu yang konstan untuk tipe ini dan perlu menghasilkan gaya if-then.

@mweerden - Ah, begitu. Terima kasih.

Saya tidak memiliki banyak pengalaman dalam C # dan .NET tetapi tampaknya para perancang bahasa tidak mengizinkan akses statis ke sistem tipe kecuali dalam keadaan sempit. The typeof kata kunci mengembalikan sebuah objek sehingga ini dapat diakses di hanya run-time.

Henk
sumber
0

Saya pikir Henk memakukannya dengan "tidak ada akses statis ke sistem tipe"

Pilihan lain adalah tidak ada urutan untuk mengetik di mana numerik dan string dapat. Jadi saklar tipe tidak akan bisa membangun pohon pencarian biner, hanya pencarian linier.

BCS
sumber
0

Saya setuju dengan komentar ini bahwa menggunakan pendekatan berbasis tabel seringkali lebih baik.

Dalam C # 1.0 ini tidak mungkin karena tidak memiliki generik dan delegasi anonim. Versi baru C # memiliki perancah untuk membuat pekerjaan ini. Memiliki notasi untuk objek literal juga membantu.

HS.
sumber
0

Saya hampir tidak memiliki pengetahuan tentang C #, tetapi saya curiga salah satu saklar hanya diambil seperti yang terjadi dalam bahasa lain tanpa berpikir untuk membuatnya lebih umum atau pengembang memutuskan bahwa memperpanjang itu tidak sepadan.

Sebenarnya Anda benar bahwa tidak ada alasan untuk menerapkan pembatasan ini. Orang mungkin menduga bahwa alasannya adalah bahwa untuk kasus-kasus yang diizinkan implementasi sangat efisien (seperti yang disarankan oleh Brian Ensink ( 44921 )), tetapi saya ragu implementasinya sangat efisien (wrt jika-pernyataan) jika saya menggunakan bilangan bulat dan beberapa kasus acak (mis. 345, -4574 dan 1234203). Dan dalam hal apa pun, apa salahnya membiarkannya untuk segalanya (atau setidaknya lebih) dan mengatakan bahwa itu hanya efisien untuk kasus tertentu (seperti (hampir) nomor berurutan).

Saya dapat, bagaimanapun, membayangkan bahwa seseorang mungkin ingin mengecualikan jenis karena alasan seperti yang diberikan oleh lomaxx ( 44918 ).

Sunting: @Henk ( 44970 ): Jika String dibagi secara maksimal, string dengan konten yang sama akan menjadi pointer ke lokasi memori yang sama juga. Kemudian, jika Anda dapat memastikan bahwa string yang digunakan dalam case disimpan secara berurutan dalam memori, Anda dapat mengimplementasikan switch dengan sangat efisien (yaitu dengan eksekusi dalam urutan 2 pembandingan, tambahan dan dua lompatan).

mweerden
sumber
0

C # 8 memungkinkan Anda untuk memecahkan masalah ini secara elegan dan kompak menggunakan ekspresi switch:

public string GetTypeName(object obj)
{
    return obj switch
    {
        int i => "Int32",
        string s => "String",
        { } => "Unknown",
        _ => throw new ArgumentNullException(nameof(obj))
    };
}

Sebagai hasilnya, Anda mendapatkan:

Console.WriteLine(GetTypeName(obj: 1));           // Int32
Console.WriteLine(GetTypeName(obj: "string"));    // String
Console.WriteLine(GetTypeName(obj: 1.2));         // Unknown
Console.WriteLine(GetTypeName(obj: null));        // System.ArgumentNullException

Anda dapat membaca lebih lanjut tentang fitur baru di sini .

smolchanovsky
sumber