Saya sedang menulis kode ini:
private static Expression<Func<Binding, bool>> ToExpression(BindingCriterion criterion)
{
switch (criterion.ChangeAction)
{
case BindingType.Inherited:
var action = (byte)ChangeAction.Inherit;
return (x => x.Action == action);
case BindingType.ExplicitValue:
var action = (byte)ChangeAction.SetValue;
return (x => x.Action == action);
default:
// TODO: Localize errors
throw new InvalidOperationException("Invalid criterion.");
}
}
Dan terkejut menemukan kesalahan kompilasi:
Variabel lokal bernama 'tindakan' sudah didefinisikan dalam lingkup ini
Itu adalah masalah yang cukup mudah untuk diselesaikan; menyingkirkan yang kedua var
sudah cukup.
Jelas variabel yang dideklarasikan dalam case
blok memiliki ruang lingkup induk switch
, tapi saya ingin tahu mengapa ini. Mengingat bahwa C # tidak memungkinkan eksekusi jatuh melalui kasus-kasus lain (itu membutuhkan break
, return
, throw
, atau goto case
pernyataan pada akhir setiap case
blok), tampaknya cukup aneh bahwa hal itu akan memungkinkan deklarasi variabel di dalam salah satu case
untuk digunakan atau konflik dengan variabel di lain case
. Dengan kata lain variabel tampaknya jatuh melalui case
pernyataan meskipun eksekusi tidak bisa. C # bersusah payah untuk mempromosikan keterbacaan dengan melarang beberapa konstruksi bahasa lain yang membingungkan atau mudah disalahgunakan. Tapi ini sepertinya hanya menyebabkan kebingungan. Pertimbangkan skenario berikut:
Jika ingin mengubahnya menjadi ini:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: return (x => x.Action == action);
Saya mendapatkan " Penggunaan 'tindakan' variabel lokal yang belum ditetapkan ". Ini membingungkan karena dalam setiap konstruksi lain di C # yang dapat saya pikirkan
var action = ...
akan menginisialisasi variabel, tetapi di sini hanya menyatakannya.Jika saya menukar kasus seperti ini:
case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; return (x => x.Action == action); case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action);
Saya mendapatkan " Tidak dapat menggunakan 'tindakan' variabel lokal sebelum dideklarasikan ". Jadi urutan blok kasus tampaknya penting di sini dengan cara yang tidak sepenuhnya jelas - Biasanya saya bisa menulis ini dalam urutan apa pun yang saya inginkan, tetapi karena
var
harus muncul di blok pertama di manaaction
digunakan, saya harus mengubahcase
blok demikian.Jika ingin mengubahnya menjadi ini:
case BindingType.Inherited: var action = (byte)ChangeAction.Inherit; return (x => x.Action == action); case BindingType.ExplicitValue: action = (byte)ChangeAction.SetValue; goto case BindingType.Inherited;
Maka saya tidak mendapatkan kesalahan, tetapi dalam arti, sepertinya variabel diberi nilai sebelum dideklarasikan.
(Meskipun saya tidak dapat memikirkan kapan pun Anda benar-benar ingin melakukan ini - saya bahkan tidak tahugoto case
ada sebelum hari ini)
Jadi pertanyaan saya adalah, mengapa para desainer C # tidak memberikan case
ruang lingkup lokal mereka sendiri? Apakah ada alasan historis atau teknis untuk ini?
sumber
action
variabel Anda sebelumswitch
pernyataan, atau masukkan setiap case ke dalam kurungannya sendiri, dan Anda akan mendapatkan perilaku yang masuk akal.switch
sama sekali - Saya hanya ingin tahu tentang alasan di balik desain ini.break
tidak mungkin dalam C #.Jawaban:
Saya pikir alasan yang baik adalah bahwa dalam setiap kasus lain, ruang lingkup variabel lokal "normal" adalah blok yang dibatasi oleh kurung kurawal (
{}
). Variabel lokal yang tidak normal muncul dalam konstruk khusus sebelum pernyataan (yang biasanya merupakan blok), sepertifor
variabel loop atau variabel yang dideklarasikan dalamusing
.Satu lagi pengecualian adalah variabel lokal dalam ekspresi kueri LINQ, tetapi itu sama sekali berbeda dari deklarasi variabel lokal normal, jadi saya tidak berpikir ada kemungkinan kebingungan di sana.
Untuk referensi, aturannya ada di §3.7 Cakupan spesifikasi C #:
(Meskipun saya tidak sepenuhnya yakin mengapa
switch
blok disebutkan secara eksplisit, karena tidak memiliki sintaks khusus untuk pernyataan variabel lokal, tidak seperti semua konstruksi yang disebutkan lainnya.)sumber
switches
tampaknya berperilaku identik dalam hal ini (kecuali untuk memerlukan lompatan pada akhircase
's). Tampaknya perilaku ini disalin dari sana. Jadi saya kira jawabannya adalah -case
pernyataan tidak membuat blok, mereka hanya mendefinisikan pembagianswitch
blok - dan karena itu tidak memiliki cakupan sendiri.Tapi itu benar. Anda dapat membuat cakupan lokal di mana saja dengan membungkus garis dengan
{}
sumber
Saya akan mengutip Eric Lippert, yang jawabannya cukup jelas pada subjek:
Jadi, kecuali Anda lebih suka bergaul dengan tim pengembang C # tahun 1999 daripada Eric Lippert, Anda tidak akan pernah tahu alasan pastinya!
sumber
Penjelasannya sederhana - itu karena seperti itu di C. Bahasa seperti C ++, Java dan C # telah menyalin sintaks dan pelingkupan dari pernyataan pergantian demi keakraban.
(Sebagaimana dinyatakan dalam jawaban lain, para pengembang C # tidak memiliki dokumentasi tentang bagaimana keputusan ini mengenai lingkup kasus dibuat. Tetapi prinsip tidak tertulis untuk sintaksis C # adalah bahwa kecuali mereka memiliki alasan kuat untuk melakukannya secara berbeda, mereka menyalin Jawa. )
Di C, pernyataan kasus mirip dengan label-goto. Pernyataan peralihan benar-benar sintaks yang lebih bagus untuk goto yang dikomputasi . Kasing menentukan titik masuk ke dalam blok sakelar. Secara default sisa kode akan dieksekusi, kecuali ada jalan keluar yang eksplisit. Jadi masuk akal jika mereka menggunakan cakupan yang sama.
(Lebih mendasar. Goto tidak terstruktur - mereka tidak mendefinisikan atau membatasi bagian kode, mereka hanya menentukan titik lompatan. Karenanya label goto tidak dapat memperkenalkan ruang lingkup.)
C # mempertahankan sintaksisnya, tetapi memperkenalkan perlindungan terhadap "fall through" dengan meminta jalan keluar setelah setiap klausa kasus (tidak kosong). Tetapi ini mengubah cara kita berpikir tentang peralihan! Kasing sekarang adalah cabang alternatif , seperti cabang di if-else. Ini berarti kita akan mengharapkan setiap cabang untuk mendefinisikan ruang lingkupnya sendiri seperti klausa if atau klausa iterasi.
Jadi singkatnya: Kasus memiliki cakupan yang sama karena ini adalah bagaimana di C. Tetapi tampaknya aneh dan tidak konsisten dalam C # karena kami menganggap kasus sebagai cabang alternatif daripada target goto.
sumber
Cara yang disederhanakan untuk melihat ruang lingkup adalah dengan mempertimbangkan ruang lingkup dengan blok
{}
.Karena
switch
tidak mengandung blok apa pun, itu tidak dapat memiliki cakupan yang berbeda.sumber
for (int i = 0; i < n; i++) Write(i); /* legal */ Write(i); /* illegal */
? Tidak ada blok, tetapi ada cakupan yang berbeda.for
pernyataan itu.switch
tidak membuat blok untuk setiap kasus, hanya di tingkat atas. Kesamaannya adalah bahwa setiap pernyataan menciptakan satu blok (dihitungswitch
sebagai pernyataan).case
saja?case
tidak mengandung blok, kecuali jika Anda memutuskan untuk menambahkannya secara opsional.for
Berlangsung untuk pernyataan berikutnya kecuali Anda menambahkan{}
sehingga selalu memiliki blok, tetapicase
berlangsung sampai sesuatu menyebabkan Anda meninggalkan blok nyata,switch
pernyataan.