beralih dengan perilaku aneh var / null

91

Diberikan kode berikut:

string someString = null;
switch (someString)
{
    case string s:
        Console.WriteLine("string s");
        break;
    case var o:
        Console.WriteLine("var o");
        break;
    default:
        Console.WriteLine("default");
        break;
}

Mengapa pernyataan sakelar cocok case var o?

Pemahaman saya case string stidak cocok ketika s == nullkarena (secara efektif) (null as string) != nullmengevaluasi salah. IntelliSense pada VS Code memberi tahu saya bahwa oitu stringjuga. Ada pemikiran?


Mirip dengan: C # 7 switch case dengan pemeriksaan null

budi
sumber
9
Dikonfirmasi. Saya suka pertanyaan ini, terutama dengan pengamatan bahwa oini string(dikonfirmasi dengan obat generik - yaitu Foo(o)di mana Foo<T>(T template) => typeof(T).Name) - itu adalah kasus yang sangat menarik di mana string xberperilaku berbeda dari var xbahkan ketika xdiketik (oleh kompilator) sebagaistring
Marc Gravell
7
Kasus default adalah kode mati. Percaya kami harus mengeluarkan peringatan di sana. Memeriksa.
JaredPar
13
Aneh bagi saya bahwa desainer C # memutuskan untuk mengizinkan vardalam konteks ini sama sekali. Itu sepertinya hal yang akan saya temukan di C ++, bukan dalam bahasa yang dimaksudkan untuk mengarahkan programmer "ke lubang kesuksesan". Di sini, varkeduanya ambigu dan tidak berguna, hal-hal yang biasanya ingin dihindari oleh desain C #.
Peter Duniho
1
@PeterDuniho Saya tidak akan mengatakan tidak berguna; ekspresi masuk ke switchbisa tidak dapat diucapkan - tipe anonim, dll; dan itu tidak ambigu - kompilator mengetahui dengan jelas tipenya; hanya membingungkan (setidaknya bagi saya) bahwa nullaturannya sangat berbeda!
Marc Gravell
1
Fakta menyenangkan @PeterDuniho - kita pernah mencari aturan formal tugas yang pasti dari spesifikasi C # 1.2, dan kode ekspansi ilustratif memiliki deklarasi variabel di dalam blok (di mana sekarang); itu hanya dipindahkan ke luar di 2.0, lalu kembali ke dalam lagi ketika masalah penangkapan sudah jelas.
Marc Gravell

Jawaban:

69

Di dalam switchpernyataan pencocokan pola yang menggunakan a caseuntuk tipe eksplisit menanyakan apakah nilai yang dimaksud adalah dari tipe spesifik itu, atau tipe turunan. Ini sama persis denganis

switch (someString) {
  case string s:
}
if (someString is string) 

Nilai nulltidak memiliki tipe dan karenanya tidak memenuhi salah satu kondisi di atas. Jenis statis someStringtidak ikut bermain di kedua contoh.

The varTipe meskipun dalam pencocokan pola bertindak sebagai kartu liar dan akan cocok dengan nilai apapun termasuk null.

The defaultterjadi di sini adalah kode mati. The case var oakan ditemukan nilai, null atau non-null. Kasus non-default selalu menang atas kasus default sehingga defaulttidak akan pernah terkena. Jika Anda melihat IL Anda akan melihat itu bahkan tidak dipancarkan.

Sekilas mungkin tampak aneh bahwa kompilasi ini tanpa peringatan apa pun (pasti membuat saya bingung). Tapi ini cocok dengan perilaku C # yang kembali ke 1.0. Kompilator mengizinkan defaultkasus bahkan ketika ia dapat dengan mudah membuktikan bahwa ia tidak akan pernah terpukul. Perhatikan sebagai contoh berikut ini:

bool b = ...;
switch (b) {
  case true: ...
  case false: ...
  default: ...
}

Disini defaulttidak akan pernah terkena (bahkan untuk boolyang memiliki nilai bukan 1 atau 0). Namun C # telah mengizinkan ini sejak 1.0 tanpa peringatan. Pencocokan pola hanya sejalan dengan perilaku ini di sini.

JaredPar
sumber
4
Masalah sebenarnya adalah bahwa compiler "menunjukkan" varmenjadi tipe stringketika sebenarnya tidak (sejujurnya tidak yakin tipe apa yang harus diakui)
shmuelie
@shmuelie jenis vardalam contoh dihitung menjadi string.
JaredPar
5
@JaredPar terima kasih atas wawasan di sini; Secara pribadi saya akan mendukung lebih banyak pemancaran peringatan bahkan ketika itu tidak dilakukan sebelumnya, tetapi saya memahami kendala tim bahasa. Pernahkah Anda mempertimbangkan "mode mengeluh tentang segala sesuatu" (mungkin diaktifkan secara default), vs "mode stoic lama" (elektif)? mungkincsc /stiffUpperLip
Marc Gravell
3
@MarcGravell kami memiliki fitur yang disebut gelombang peringatan yang dimaksudkan untuk membuatnya lebih mudah, tidak terlalu merusak, untuk memperkenalkan peringatan baru. Pada dasarnya setiap rilis kompilator adalah gelombang baru dan Anda dapat memilih peringatan melalui / gelombang: 1, / gelombang: 2, / gelombang: semua.
JaredPar
4
@JonathanDickinson Saya rasa itu tidak menunjukkan apa yang menurut Anda ditunjukkannya. Itu hanya menunjukkan a nulladalah stringreferensi yang valid , dan stringreferensi apa pun (termasuk null) dapat secara implisit dilemparkan (memelihara referensi) ke objectreferensi, dan objectreferensi apa pun yang nulldapat berhasil disebarluaskan (eksplisit) ke jenis lain, masih ada null. Tidak benar-benar hal yang sama dalam hal sistem tipe kompilator.
Marc Gravell
22

Saya mengumpulkan beberapa komentar twitter di sini - ini sebenarnya baru bagi saya, dan saya berharap jaredpar akan memberikan jawaban yang lebih komprehensif, tetapi; versi pendek yang saya pahami:

case string s:

ditafsirkan sebagai if(someString is string) { s = (string)someString; ...atau if((s = (someString as string)) != null) { ... }- salah satunya melibatkan nulltes - yang gagal dalam kasus Anda; sebaliknya:

case var o:

di mana kompilator menyelesaikan oapa stringadanya o = (string)someString; ...- tidak ada nullpengujian, meskipun faktanya terlihat serupa di permukaan, hanya dengan kompilator yang menyediakan tipenya.

akhirnya:

default:

di sini tidak dapat dihubungi , karena kasus di atas mencakup semuanya. Ini mungkin bug kompilator karena tidak mengeluarkan peringatan kode yang tidak dapat dijangkau.

Saya setuju bahwa ini sangat halus dan bernuansa, dan membingungkan. Namun ternyata case var oskenario tersebut digunakan dengan propagasi null ( o?.Length ?? 0dll). Saya setuju bahwa aneh bahwa ini bekerja sangat berbeda antara var odan string s, tetapi itulah yang dilakukan kompilator saat ini.

Marc Gravell
sumber
14

Itu karena case <Type>cocok pada jenis dinamis (waktu proses), bukan jenis statis (waktu kompilasi). nulltidak memiliki tipe dinamis, jadi tidak bisa ditandingi string. varhanyalah fallback.

(Memposting karena saya suka jawaban singkat.)

pengguna541686
sumber