Mengapa pernyataan itu (j ++); terlarang?

169

Kode berikut salah (lihat di ideone ):

public class Test
{
    public static void Main()
    {
        int j = 5;
        (j++);      // if we remove the "(" and ")" then this compiles fine.
    }
}

kesalahan CS0201: Hanya penugasan, panggilan, kenaikan, penurunan, menunggu, dan ekspresi objek baru yang dapat digunakan sebagai pernyataan

  1. Mengapa kode dikompilasi ketika kita menghapus tanda kurung?
  2. Mengapa tidak dikompilasi dengan tanda kurung?
  3. Mengapa C # didesain seperti itu?
pengguna10607
sumber
29
@Servy banyak orang yang terlibat dengan desain bahasa C # ada di SO, jadi itu sebabnya saya bertanya. Ada yang salah dengan ini?
user10607
34
Saya tidak dapat membayangkan pertanyaan yang lebih banyak pada topik daripada "mengapa bahasa pemrograman tertentu menangani perilaku khusus ini dengan cara ini?"
Mage Xy
20
Sekarang kami memiliki jawaban Eric Lippert. Mungkin Anda ingin memikirkan kembali keputusan Anda tentang jawaban yang diterima. Ini Natal, jadi mungkin Anda sudah menerima jawabannya terlalu dini.
Thomas Weller
17
@Ervy. Apakah Anda menyiratkan bahwa keputusan desain tidak pernah didokumentasikan dan sama sekali tidak diketahui orang lain? Dia tidak meminta pengguna SO untuk menebak, dia meminta jawaban - itu tidak berarti dia meminta tebakan. Apakah orang menjawab dengan menebak ada pada mereka , bukan dia. Dan seperti yang ditunjukkan OP, ada orang - orang di stack overflow yang benar - benar bekerja pada C # dan membuat keputusan ini.
Rob
5
@Rob: Masalahnya adalah, ya, kecuali alasannya sudah didokumentasikan di suatu tempat, itu harus, tetapi spekulasi itu menyenangkan dan siapa yang tidak ingin berbagi miliknya sendiri? Jika Anda menemukan cara untuk memastikan tebakan murni (atau "berpendidikan") seperti itu dimusnahkan, katakan padaku dan kami akan menghasilkan banyak uang.
Deduplicator

Jawaban:

221

Wawasan mendalam dihargai.

Saya akan melakukan yang terbaik.

Seperti jawaban lain yang dicatat, apa yang terjadi di sini adalah kompiler mendeteksi bahwa ekspresi sedang digunakan sebagai pernyataan . Dalam banyak bahasa - C, JavaScript, dan banyak lainnya - sangat sah untuk menggunakan ekspresi sebagai pernyataan. 2 + 2;legal dalam bahasa-bahasa ini, meskipun ini adalah pernyataan yang tidak berpengaruh. Beberapa ekspresi hanya berguna untuk nilainya, beberapa ekspresi hanya berguna untuk efek sampingnya (seperti panggilan ke metode pengembalian batal) dan beberapa ekspresi, sayangnya, berguna untuk keduanya. (Seperti kenaikan.)

Poinnya adalah: pernyataan yang hanya terdiri dari ekspresi hampir pasti kesalahan kecuali jika ekspresi itu biasanya dianggap lebih berguna untuk efek sampingnya daripada nilainya . Desainer C # ingin menemukan jalan tengah, dengan memungkinkan ekspresi yang umumnya dianggap sebagai efek samping, sementara melarang mereka yang juga biasanya dianggap berguna untuk nilai-nilai mereka. Rangkaian ekspresi yang mereka identifikasi dalam C # 1.0 adalah kenaikan, penurunan, pemanggilan metode, penugasan, dan agak kontroversial, pemanggilan konstruktor.


ASIDE: Orang biasanya menganggap konstruksi objek sebagai nilai yang dihasilkannya, bukan untuk efek samping konstruksi; menurut saya memungkinkan new Foo();adalah sedikit kesalahan. Secara khusus, saya telah melihat pola ini dalam kode dunia nyata yang menyebabkan cacat keamanan:

catch(FooException ex) { new BarException(ex); } 

Bisa jadi sangat sulit untuk menemukan cacat ini jika kodenya rumit.


Oleh karena itu kompiler berfungsi untuk mendeteksi semua pernyataan yang terdiri dari ekspresi yang tidak ada dalam daftar itu. Secara khusus, ekspresi dalam tanda kurung diidentifikasi hanya dengan itu - ekspresi dalam tanda kurung. Mereka tidak ada dalam daftar "diizinkan sebagai ekspresi pernyataan", sehingga mereka tidak diizinkan.

Semua ini demi prinsip desain bahasa C #. Jika Anda mengetik, (x++);Anda mungkin melakukan kesalahan . Ini mungkin salah ketik M(x++);atau sesuatu yang adil. Ingat, sikap tim kompiler C # bukanlah " bisakah kita mencari cara untuk membuat ini berhasil? " Sikap tim kompiler C # adalah " jika kode yang masuk akal tampak seperti kesalahan yang mungkin terjadi, mari beri tahu pengembang ". Pengembang C # menyukai sikap itu.

Sekarang, semua yang mengatakan, sebenarnya ada sebuah kasus aneh beberapa di mana C # spesifikasi yang menyiratkan atau negara langsung yang kurung dianulir tetapi C # compiler memungkinkan mereka anyways. Dalam hampir semua kasus perbedaan kecil antara perilaku yang ditentukan dan perilaku yang diizinkan benar-benar tidak berbahaya, sehingga penulis kompiler tidak pernah memperbaiki bug kecil ini. Anda dapat membaca tentang itu di sini:

Apakah ada perbedaan antara return myVar vs return (myVar)?

Eric Lippert
sumber
5
Re: "Orang biasanya menganggap doa konstruktor sebagai digunakan untuk nilai yang dihasilkannya, bukan untuk efek samping konstruksi; menurut pendapat saya ini adalah sedikit kesalahan": Saya membayangkan satu faktor dalam keputusan ini adalah bahwa jika compiler melarangnya, ada tidak akan ada perbaikan bersih dalam kasus di mana Anda sedang menelepon untuk efek samping. (Sebagian besar pernyataan ekspresi terlarang lainnya memiliki bagian-bagian luar yang tidak memiliki tujuan dan dapat dihapus, dengan pengecualian ... ? ... : ..., di mana perbaikannya menggunakan if/ elsesebagai gantinya.)
ruakh
1
@ruakh: Karena ini adalah perilaku yang harus dicegah, perbaikan yang bersih tidak diperlukan, selama ada perbaikan yang cukup murah / sederhana. Yang ada; tetapkan ke variabel dan jangan gunakan variabel itu. Jika Anda ingin terang-terangan tidak menggunakannya, berikan baris kode itu cakupannya sendiri. Meskipun secara teknis orang dapat berargumen bahwa ini mengubah semantik kode (tugas referensi yang tidak berguna masih menjadi tugas referensi yang tidak berguna), dalam praktiknya tidak mungkin menjadi masalah. Saya akui itu memang menghasilkan IL yang sedikit berbeda ... tetapi hanya jika dikompilasi tanpa optimasi.
Brian
Safari, Firefox, dan Chrome semuanya memberikan ReferenceError saat mengetik contineu;.
Brian McCutchon
2
@ McBrainy: Anda benar. Saya salah mengingat cacat yang diakibatkan kesalahan ejaan; Saya akan memeriksa catatan saya. Sementara itu saya telah menghapus pernyataan yang menyinggung karena tidak berhubungan dengan poin yang lebih besar di sini.
Eric Lippert
1
@ Mike: Saya setuju. Situasi lain di mana kita kadang-kadang melihat pernyataan itu adalah kasus uji seperti try { new Foo(null); } catch (ArgumentNullException)...tapi jelas situasi ini secara definisi bukan kode produksi. Tampaknya masuk akal bahwa kode tersebut dapat ditulis untuk menetapkan variabel dummy.
Eric Lippert
46

Dalam spesifikasi bahasa C #

Pernyataan ekspresi digunakan untuk mengevaluasi ekspresi. Ekspresi yang dapat digunakan sebagai pernyataan termasuk pemanggilan metode, alokasi objek menggunakan operator baru, penugasan menggunakan = dan operator penugasan majemuk, operasi penambahan dan penurunan menggunakan operator ++ dan - dan menunggu ekspresi.

Menempatkan tanda kurung di sekitar pernyataan menciptakan ekspresi yang disebut tanda kurung baru. Dari spesifikasi:

Ekspresi kurung terdiri dari ekspresi terlampir dalam kurung. ... Ekspresi kurung dievaluasi dengan mengevaluasi ekspresi dalam kurung. Jika ekspresi di dalam tanda kurung menunjukkan namespace atau tipe, kesalahan waktu kompilasi terjadi. Kalau tidak, hasil dari ekspresi yang di-kurung adalah hasil dari evaluasi dari ekspresi yang terkandung.

Karena ekspresi yang ditulis dalam tanda kurung tidak terdaftar sebagai pernyataan ekspresi yang valid, itu bukan pernyataan yang valid sesuai dengan spesifikasi. Mengapa para perancang memilih untuk melakukannya dengan cara ini adalah dugaan siapa pun, tetapi taruhan saya adalah karena kurung tidak berfungsi jika seluruh pernyataan terkandung dalam kurung: stmtdan (stmt)persis sama.

Frank Bryce
sumber
4
@Ervy: Jika Anda membaca dengan seksama, itu sedikit lebih bernuansa dari itu. Yang disebut "Pernyataan Ekspresi" memang merupakan pernyataan yang valid, dan spesifikasinya mencantumkan jenis ekspresi yang dapat menjadi pernyataan yang valid. Namun, saya ulangi dari spesifikasi bahwa jenis ekspresi yang disebut "ekspresi yang di-kurung" BUKAN pada daftar ekspresi yang valid yang dapat digunakan sebagai pernyataan ekspresi yang valid.
Frank Bryce
4
Mereka OPtidak bertanya apa-apa tentang desain bahasa. Dia hanya ingin tahu mengapa ini kesalahan. Jawabannya adalah: karena itu bukan pernyataan yang valid .
Leandro
9
OK, salahku. Jawabannya adalah: karena, sesuai dengan spesifikasi , itu bukan pernyataan yang valid. Itu dia: alasan desain !
Leandro
3
Pertanyaannya adalah bukan menanyakan alasan yang dalam dan digerakkan oleh desain mengapa bahasa dirancang untuk menangani sintaks ini dengan cara ini - dia bertanya mengapa satu bagian kode dikompilasi ketika bagian kode lain (yang bagi para pemula sepertinya harus berperilaku identik) gagal dikompilasi. Posting ini menjawab itu.
Mage Xy
4
@Servy JohnCarpenter tidak hanya mengatakan "Anda tidak bisa melakukan itu." Dia mengatakan, dua hal yang membuatmu bingung tidak sama. j ++ adalah Pernyataan Ekspresi, sedangkan (j ++) adalah ekspresi kurung. Tiba-tiba OP sekarang tahu bedanya dan apa mereka. Itu jawaban yang bagus. Itu menjawab tubuh pertanyaannya bukan judul. Saya pikir banyak pertentangan berasal dari kata "desain" dalam judul pertanyaan, tetapi ini tidak harus dijawab oleh para desainer, hanya diperoleh dari spesifikasinya.
Thinklarge
19

karena kurung di sekitar i++sedang membuat / mendefinisikan ekspresi .. seperti pesan kesalahan mengatakan .. ekspresi sederhana tidak dapat digunakan sebagai pernyataan.

mengapa bahasanya dirancang seperti ini? untuk mencegah bug, memiliki ekspresi yang menyesatkan sebagai pernyataan, yang tidak menghasilkan efek samping seperti memiliki kode

int j = 5;
j+1; 

baris kedua tidak berpengaruh (tetapi Anda mungkin tidak memperhatikan). Tapi bukannya kompiler menghapusnya (karena kode tidak diperlukan). Itu secara eksplisit meminta Anda untuk menghapusnya (sehingga Anda akan menyadari atau kesalahan) ATAU memperbaikinya jika Anda lupa mengetik sesuatu.

edit :

untuk membuat bagian tentang tanda kurung menjadi lebih jelas .. tanda kurung dalam c # (selain kegunaan lain, seperti pemeran dan pemanggilan fungsi), digunakan untuk mengelompokkan ekspresi, dan mengembalikan satu ekspresi (membuat sub ekspresi).

pada level kode itu hanya stament yang diizinkan .. jadi

j++; is a valid statement because it produces side effects

tetapi dengan menggunakan tanda kurung Anda mengubahnya menjadi ekspresi

myTempExpression = (j++)

dan ini

myTempExpression;

tidak valid karena kompiler tidak dapat memastikan bahwa ekspresi sebagai efek samping (bukan tanpa menimbulkan masalah penghentian) ..

CaldasGSM
sumber
Ya, pada awalnya saya juga berpikir begitu. Tapi ini memang punya efek samping. Itu melakukan post-increment jvariabel kan?
user10607
1
j++;adalah pernyataan yang valid, tetapi dengan menggunakan tanda kurung yang Anda ucapkan let me take this stement and turn it into an expression.. dan ekspresi tidak valid pada saat itu dalam kode
CaldasGSM
Sayang sekali tidak ada bentuk pernyataan "hitung dan abaikan", karena ada kalanya kode mungkin ingin menyatakan bahwa suatu nilai dapat dihitung tanpa melempar pengecualian, tetapi tidak peduli dengan nilai aktual yang dikomputasi. Pernyataan komputasi-dan-abaikan tidak akan sering digunakan, tetapi akan membuat niat programmer jelas dalam kasus seperti itu.
supercat