Anda melakukan sesuatu yang tidak masuk akal dan kemudian tidak menyukai pesan yang dihasilkan kompilator? Ini adalah kode C # yang tidak valid ... apa yang sebenarnya ingin Anda lakukan?
Hogan
19
@ Hogan: Kode tersebut tampaknya acak dan cukup akademis untuk pertanyaan yang diajukan karena rasa ingin tahu. Tebakan saya ...
BoltClock
8
null + true mengembalikan 1 di JavaScript.
Fatih Acet
Jawaban:
147
Anehnya, ini hanya mengikuti aturan dari spesifikasi bahasa C #.
Dari bagian 7.3.4:
Operasi dalam bentuk x op y, di mana op adalah operator biner yang dapat kelebihan beban, x adalah ekspresi tipe X, dan y adalah ekspresi tipe Y, diproses sebagai berikut:
Himpunan calon operator yang ditentukan pengguna yang disediakan oleh X dan Y untuk operator operasi op (x, y) ditentukan. Himpunan ini terdiri dari gabungan operator kandidat yang disediakan oleh X dan operator kandidat yang disediakan oleh Y, masing-masing ditentukan menggunakan aturan §7.3.5. Jika X dan Y adalah tipe yang sama, atau jika X dan Y diturunkan dari tipe basis yang sama, maka operator kandidat bersama hanya terjadi dalam kumpulan gabungan satu kali.
Jika kumpulan calon operator yang ditentukan pengguna tidak kosong, maka ini menjadi kumpulan calon operator untuk operasi. Jika tidak, implementasi op operator biner yang telah ditentukan, termasuk bentuk yang diangkat, menjadi kumpulan calon operator untuk operasi tersebut. Implementasi yang telah ditentukan sebelumnya dari operator tertentu ditentukan dalam deskripsi operator (§7.8 hingga §7.12).
Aturan resolusi kelebihan beban pada §7.5.3 diterapkan ke kumpulan operator kandidat untuk memilih operator terbaik sehubungan dengan daftar argumen (x, y), dan operator ini menjadi hasil dari proses resolusi kelebihan beban. Jika resolusi kelebihan beban gagal memilih satu operator terbaik, kesalahan waktu pengikatan terjadi.
Jadi, mari kita bahas ini secara bergantian.
X adalah tipe nol di sini - atau bukan tipe sama sekali, jika Anda ingin menganggapnya seperti itu. Itu tidak memberikan kandidat apa pun. Y adalah bool, yang tidak menyediakan +operator yang ditentukan pengguna . Jadi langkah pertama tidak menemukan operator yang ditentukan pengguna.
Kompiler kemudian beralih ke poin poin kedua, melihat melalui implementasi + operator biner yang telah ditentukan dan formulir yang diangkat. Ini tercantum di bagian 7.8.4 spesifikasi.
Jika Anda melihat melalui operator yang telah ditentukan sebelumnya, satu - satunya yang berlaku adalah string operator +(string x, object y). Jadi set kandidat memiliki satu entri. Itu membuat poin terakhir sangat sederhana ... resolusi kelebihan beban memilih operator itu, memberikan tipe ekspresi keseluruhan string.
Satu hal yang menarik adalah hal ini akan terjadi meskipun ada operator yang ditentukan pengguna lain yang tersedia pada jenis yang tidak disebutkan. Sebagai contoh:
// Foo defined Foo operator+(Foo foo, bool b)Foo f =null;Foo g = f +true;
Tidak apa-apa, tetapi tidak digunakan untuk literal nol, karena kompilator tidak tahu untuk mencarinya Foo. Itu hanya tahu untuk dipertimbangkan stringkarena ini adalah operator yang ditentukan sebelumnya secara eksplisit tercantum dalam spesifikasi. (Sebenarnya, ini bukan operator yang ditentukan oleh tipe string ... 1 ) Itu berarti bahwa ini akan gagal untuk dikompilasi:
// Error: Cannot implicitly convert type 'string' to 'Foo'Foo f =null+true;
Jenis operan kedua lainnya akan menggunakan beberapa operator lain, tentu saja:
var x =null+0;// x is Nullable<int>var y =null+0L;// y is Nullable<long>var z =null+DayOfWeek.Sunday;// z is Nullable<DayOfWeek>
1 Anda mungkin bertanya - tanya mengapa tidak ada operator + string. Ini pertanyaan yang masuk akal, dan saya hanya menebak-nebak jawabannya, tetapi pertimbangkan ungkapan ini:
string x = a + b + c + d;
Jika stringtidak ada casing khusus di kompiler C #, ini akan berakhir secara efektif:
Jadi itu menciptakan dua string perantara yang tidak perlu. Namun, karena ada dukungan khusus di dalam kompilator, ia sebenarnya dapat mengkompilasi di atas sebagai:
string x =string.Concat(a, b, c, d);
yang dapat membuat hanya satu string dengan panjang yang tepat, menyalin semua data tepat satu kali. Bagus.
'memberikan tipe ekspresi keseluruhan string' Saya tidak setuju. Kesalahannya berasal dari truetidak dapat dikonversi ke string. Jika ekspresi itu valid, jenisnya akan string, tetapi dalam kasus ini kegagalan untuk mengonversi ke string membuat seluruh ekspresi menjadi error, dan karenanya tidak memiliki tipe.
leppie
6
@leppie: Ekspresi ini valid, dan tipenya adalah string. Coba "var x = null + true;" - itu mengkompilasi dan xbertipe string. Perhatikan bahwa tanda tangan yang digunakan di sini adalah string operator+(string, object)- itu diubah boolmenjadi object(yang bagus), bukan menjadi string.
Jon Skeet
Terima kasih Jon, mengerti sekarang. BTW bagian 14.7.4 dalam versi 2 spesifikasi.
leppie
@leppie: Apakah itu ada dalam penomoran ECMA? Itu selalu sangat berbeda :(
Jon Skeet
47
dalam 20 menit. Dia menulis semuanya dalam 20 menit. Dia harus menulis buku atau sesuatu ... oh tunggu.
Epaga
44
Alasannya adalah karena begitu Anda memperkenalkan +aturan pengikatan operator C # mulai berlaku. Ini akan mempertimbangkan kumpulan +operator yang tersedia dan memilih kelebihan beban terbaik. Salah satu operator tersebut adalah sebagai berikut
stringoperator+(string x,object y)
Kelebihan beban ini kompatibel dengan tipe argumen dalam ekspresi null + true. Oleh karena itu dipilih sebagai operator dan dievaluasi sebagai dasarnya ((string)null) + trueyang mengevaluasi nilai "True".
Bagian 7.7.4 dari spesifikasi bahasa C # berisi rincian seputar resolusi ini.
Saya perhatikan IL yang dihasilkan langsung memanggil Concat. Saya pikir saya akan melihat panggilan ke fungsi + operator di sana. Apakah itu hanya pengoptimalan sebaris?
Quentin-starin
1
@qstarin saya percaya alasannya adalah bahwa tidak ada benar-benar sebuah operator+untuk string. Alih-alih itu hanya ada dalam pikiran kompiler dan itu hanya menerjemahkannya menjadi panggilan untukstring.Concat
JaredPar
1
@qstarin @JaredPar: Saya membahasnya lebih dalam dalam jawaban saya.
Jon Skeet
11
Kompilator keluar mencari operator + () yang bisa mengambil argumen null terlebih dahulu. Tak satu pun dari tipe nilai standar yang memenuhi syarat, null bukanlah nilai yang valid untuk mereka. Satu-satunya kecocokan adalah System.String.operator + (), tidak ada ambiguitas.
Argumen ke-2 dari operator itu juga berupa string. Itu tidak berguna, tidak bisa secara implisit mengubah bool menjadi string.
nullakan dilemparkan ke string null, dan ada konverter implisit dari bool ke string sehingga trueakan dilemparkan ke string dan kemudian, +operator akan diterapkan: seperti: string str = "" + true.ToString ();
var b =(null+DateTime.Now);// Stringvar b =(null+1);// System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etcvar b =(null+newObject());// String | same with any ref type
Alasannya adalah kemudahan (merangkai string adalah tugas yang umum).
Seperti yang dikatakan BoltClock, operator '+' didefinisikan pada tipe numerik, string, dan juga dapat didefinisikan untuk tipe kita sendiri (operator overloading).
Jika tidak ada operator '+' yang kelebihan beban pada tipe argumen dan bukan tipe numerik, kompilator defaultnya adalah penggabungan string.
Kompilator menyisipkan panggilan ke String.Concat(...)saat Anda menggabungkan menggunakan '+', dan implementasi Concat memanggil ToString pada setiap objek yang diteruskan ke dalamnya.
Jawaban:
Anehnya, ini hanya mengikuti aturan dari spesifikasi bahasa C #.
Dari bagian 7.3.4:
Jadi, mari kita bahas ini secara bergantian.
X adalah tipe nol di sini - atau bukan tipe sama sekali, jika Anda ingin menganggapnya seperti itu. Itu tidak memberikan kandidat apa pun. Y adalah
bool
, yang tidak menyediakan+
operator yang ditentukan pengguna . Jadi langkah pertama tidak menemukan operator yang ditentukan pengguna.Kompiler kemudian beralih ke poin poin kedua, melihat melalui implementasi + operator biner yang telah ditentukan dan formulir yang diangkat. Ini tercantum di bagian 7.8.4 spesifikasi.
Jika Anda melihat melalui operator yang telah ditentukan sebelumnya, satu - satunya yang berlaku adalah
string operator +(string x, object y)
. Jadi set kandidat memiliki satu entri. Itu membuat poin terakhir sangat sederhana ... resolusi kelebihan beban memilih operator itu, memberikan tipe ekspresi keseluruhanstring
.Satu hal yang menarik adalah hal ini akan terjadi meskipun ada operator yang ditentukan pengguna lain yang tersedia pada jenis yang tidak disebutkan. Sebagai contoh:
Tidak apa-apa, tetapi tidak digunakan untuk literal nol, karena kompilator tidak tahu untuk mencarinya
Foo
. Itu hanya tahu untuk dipertimbangkanstring
karena ini adalah operator yang ditentukan sebelumnya secara eksplisit tercantum dalam spesifikasi. (Sebenarnya, ini bukan operator yang ditentukan oleh tipe string ... 1 ) Itu berarti bahwa ini akan gagal untuk dikompilasi:Jenis operan kedua lainnya akan menggunakan beberapa operator lain, tentu saja:
1 Anda mungkin bertanya - tanya mengapa tidak ada operator + string. Ini pertanyaan yang masuk akal, dan saya hanya menebak-nebak jawabannya, tetapi pertimbangkan ungkapan ini:
Jika
string
tidak ada casing khusus di kompiler C #, ini akan berakhir secara efektif:Jadi itu menciptakan dua string perantara yang tidak perlu. Namun, karena ada dukungan khusus di dalam kompilator, ia sebenarnya dapat mengkompilasi di atas sebagai:
yang dapat membuat hanya satu string dengan panjang yang tepat, menyalin semua data tepat satu kali. Bagus.
sumber
true
tidak dapat dikonversi kestring
. Jika ekspresi itu valid, jenisnya akanstring
, tetapi dalam kasus ini kegagalan untuk mengonversi ke string membuat seluruh ekspresi menjadi error, dan karenanya tidak memiliki tipe.x
bertipestring
. Perhatikan bahwa tanda tangan yang digunakan di sini adalahstring operator+(string, object)
- itu diubahbool
menjadiobject
(yang bagus), bukan menjadistring
.Alasannya adalah karena begitu Anda memperkenalkan
+
aturan pengikatan operator C # mulai berlaku. Ini akan mempertimbangkan kumpulan+
operator yang tersedia dan memilih kelebihan beban terbaik. Salah satu operator tersebut adalah sebagai berikutKelebihan beban ini kompatibel dengan tipe argumen dalam ekspresi
null + true
. Oleh karena itu dipilih sebagai operator dan dievaluasi sebagai dasarnya((string)null) + true
yang mengevaluasi nilai"True"
.Bagian 7.7.4 dari spesifikasi bahasa C # berisi rincian seputar resolusi ini.
sumber
operator+
untukstring
. Alih-alih itu hanya ada dalam pikiran kompiler dan itu hanya menerjemahkannya menjadi panggilan untukstring.Concat
Kompilator keluar mencari operator + () yang bisa mengambil argumen null terlebih dahulu. Tak satu pun dari tipe nilai standar yang memenuhi syarat, null bukanlah nilai yang valid untuk mereka. Satu-satunya kecocokan adalah System.String.operator + (), tidak ada ambiguitas.
Argumen ke-2 dari operator itu juga berupa string. Itu tidak berguna, tidak bisa secara implisit mengubah bool menjadi string.
sumber
Menariknya, menggunakan Reflector untuk memeriksa apa yang dihasilkan, kode berikut:
diubah menjadi ini oleh kompilator:
Alasan di balik "pengoptimalan" ini agak aneh yang harus saya katakan, dan tidak sesuai dengan pilihan operator yang saya harapkan.
Juga, kode berikut ini:
diubah menjadi
dimana
string b = true;
sebenarnya tidak diterima oleh kompilator.sumber
null
akan dilemparkan ke string null, dan ada konverter implisit dari bool ke string sehinggatrue
akan dilemparkan ke string dan kemudian,+
operator akan diterapkan: seperti: string str = "" + true.ToString ();jika Anda memeriksanya dengan Ildasm:
string str = null + true;
itu seperti di bawah ini:
sumber
Gila?? Tidak, pasti ada alasan di baliknya.
Seseorang menelepon
Eric Lippert
...sumber
Alasannya adalah kemudahan (merangkai string adalah tugas yang umum).
Seperti yang dikatakan BoltClock, operator '+' didefinisikan pada tipe numerik, string, dan juga dapat didefinisikan untuk tipe kita sendiri (operator overloading).
Jika tidak ada operator '+' yang kelebihan beban pada tipe argumen dan bukan tipe numerik, kompilator defaultnya adalah penggabungan string.
Kompilator menyisipkan panggilan ke
String.Concat(...)
saat Anda menggabungkan menggunakan '+', dan implementasi Concat memanggil ToString pada setiap objek yang diteruskan ke dalamnya.sumber