Pertanyaan ini menjadi subjek blog saya pada tanggal 20 September 2010 . Jawaban Josh dan Chad ("mereka tidak menambah nilai jadi mengapa membutuhkannya?" Dan "untuk menghilangkan redundansi") pada dasarnya benar. Untuk menyempurnakannya sedikit lagi:
Fitur yang memungkinkan Anda untuk memilih daftar argumen sebagai bagian dari "fitur yang lebih besar" dari penginisialisasi objek memenuhi bilah kami untuk fitur "manis". Beberapa poin yang kami pertimbangkan:
- biaya desain dan spesifikasi rendah
- kami akan secara ekstensif mengubah kode parser yang menangani pembuatan objek; biaya pengembangan tambahan untuk membuat daftar parameter opsional tidak besar dibandingkan dengan biaya fitur yang lebih besar
- beban pengujian relatif kecil dibandingkan dengan biaya fitur yang lebih besar
- beban dokumentasi relatif kecil dibandingkan ...
- beban pemeliharaan diantisipasi menjadi kecil; Saya tidak ingat ada bug yang dilaporkan dalam fitur ini selama bertahun-tahun sejak dikirim.
- fitur tersebut tidak menimbulkan risiko langsung yang jelas terhadap fitur masa depan di area ini. (Hal terakhir yang ingin kami lakukan adalah membuat fitur yang murah dan mudah sekarang yang membuatnya lebih sulit untuk menerapkan fitur yang lebih menarik di masa mendatang.)
- fitur tersebut tidak menambahkan ambiguitas baru pada analisis leksikal, gramatikal, atau semantik bahasa tersebut. Ini tidak menimbulkan masalah untuk jenis analisis "program parsial" yang dilakukan oleh mesin "IntelliSense" IDE saat Anda mengetik. Dan seterusnya.
- fitur mencapai "sweet spot" umum untuk fitur inisialisasi objek yang lebih besar; biasanya jika Anda menggunakan penginisialisasi objek justru karena konstruktor objek tidak mengizinkan Anda untuk menyetel properti yang Anda inginkan. Sangat umum untuk objek seperti itu hanya menjadi "tas properti" yang tidak memiliki parameter di ctor sejak awal.
Lalu mengapa Anda tidak juga membuat tanda kurung kosong opsional dalam panggilan konstruktor default dari ekspresi pembuatan objek yang tidak memiliki penginisialisasi objek?
Perhatikan kembali daftar kriteria di atas. Salah satunya adalah bahwa perubahan tersebut tidak menimbulkan ambiguitas baru dalam analisis leksikal, gramatikal atau semantik suatu program. Perubahan yang Anda usulkan memang memperkenalkan ambiguitas analisis semantik:
class P
{
class B
{
public class M { }
}
class C : B
{
new public void M(){}
}
static void Main()
{
new C().M(); // 1
new C.M(); // 2
}
}
Baris 1 membuat C baru, memanggil konstruktor default, lalu memanggil metode instance M pada objek baru. Baris 2 membuat instance baru BM dan memanggil konstruktor defaultnya. Jika tanda kurung pada baris 1 bersifat opsional maka baris 2 akan menjadi ambigu. Kami kemudian harus membuat aturan untuk menyelesaikan ambiguitas; kami tidak dapat membuatnya menjadi kesalahan karena itu akan menjadi perubahan yang merusak yang mengubah program C # legal yang ada menjadi program rusak.
Oleh karena itu, aturannya harus sangat rumit: pada dasarnya tanda kurung hanya opsional jika tidak menimbulkan ambiguitas. Kami harus menganalisis semua kemungkinan kasus yang menyebabkan ambiguitas dan kemudian menulis kode di compiler untuk mendeteksinya.
Dalam terang itu, kembali dan lihat semua biaya yang saya sebutkan. Berapa banyak dari mereka sekarang menjadi besar? Aturan yang rumit memiliki biaya desain, spesifikasi, pengembangan, pengujian, dan dokumentasi yang besar. Aturan yang rumit lebih cenderung menyebabkan masalah dengan interaksi tak terduga dengan fitur di masa mendatang.
Semuanya untuk apa? Manfaat pelanggan kecil yang tidak menambahkan kekuatan representasi baru ke bahasa, tetapi menambahkan kasus sudut gila hanya menunggu untuk meneriakkan "gotcha" pada beberapa jiwa malang yang tidak curiga yang menabraknya. Fitur seperti itu segera dipotong dan dimasukkan ke dalam daftar "jangan pernah lakukan ini".
Bagaimana Anda menentukan ambiguitas itu?
Yang itu segera menjadi jelas; Saya cukup akrab dengan aturan di C # untuk menentukan kapan nama putus-putus diharapkan.
Saat mempertimbangkan fitur baru, bagaimana Anda menentukan apakah fitur itu menyebabkan ambiguitas? Dengan tangan, dengan bukti resmi, dengan analisis mesin, apa?
Ketiganya. Sebagian besar kita hanya melihat spesifikasi dan mie di atasnya, seperti yang saya lakukan di atas. Misalnya, kita ingin menambahkan operator awalan baru ke C # yang disebut "frob":
x = frob 123 + 456;
(UPDATE: frob
tentu saja await
; analisis di sini pada dasarnya adalah analisis yang dilakukan tim desain saat menambahkan await
.)
"frob" di sini seperti "baru" atau "++" - ia muncul sebelum semacam ekspresi. Kami akan mengerjakan prioritas dan asosiatif yang diinginkan dan seterusnya, dan kemudian mulai mengajukan pertanyaan seperti "bagaimana jika program sudah memiliki jenis, bidang, properti, peristiwa, metode, konstanta, atau lokal yang disebut frob?" Itu akan segera mengarah pada kasus seperti:
frob x = 10;
apakah itu berarti "lakukan operasi frob pada hasil x = 10, atau buat variabel berjenis frob yang disebut x dan tetapkan 10 untuk itu?" (Atau, jika frobbing menghasilkan variabel, itu bisa menjadi penugasan 10 ke frob x
. Lagi pula, *x = 10;
parsing dan legal jika x
ada int*
.)
G(frob + x)
Apakah itu berarti "frob hasil dari operator plus unary pada x" atau "tambahkan ekspresi frob ke x"?
Dan seterusnya. Untuk mengatasi ambiguitas ini, kami mungkin memperkenalkan heuristik. Saat Anda mengucapkan "var x = 10;" itu ambigu; ini bisa berarti "menyimpulkan tipe dari x" atau bisa juga berarti "x adalah tipe var". Jadi kita memiliki heuristik: pertama kita mencoba mencari tipe bernama var, dan hanya jika tidak ada kita menyimpulkan tipe x.
Atau, kami mungkin mengubah sintaks agar tidak ambigu. Ketika mereka merancang C # 2.0, mereka mengalami masalah ini:
yield(x);
Apakah itu berarti "hasil x dalam sebuah iterator" atau "panggil metode hasil dengan argumen x?" Dengan mengubahnya menjadi
yield return(x);
sekarang tidak ambigu.
Dalam kasus parens opsional dalam penginisialisasi objek, sangat mudah untuk mempertimbangkan apakah ada ambiguitas yang diperkenalkan atau tidak karena jumlah situasi di mana diperbolehkan untuk memperkenalkan sesuatu yang dimulai dengan {sangat kecil . Pada dasarnya hanya berbagai konteks pernyataan, pernyataan lambdas, penginisialisasi array dan hanya itu. Sangat mudah untuk bernalar melalui semua kasus dan menunjukkan bahwa tidak ada ambiguitas. Memastikan IDE tetap efisien agak lebih sulit tetapi dapat dilakukan tanpa terlalu banyak masalah.
Mengotak-atik spesifikasi seperti ini biasanya sudah cukup. Jika itu adalah fitur yang sangat rumit maka kami mengeluarkan alat yang lebih berat. Misalnya, saat mendesain LINQ, salah satu orang kompiler dan salah satu orang IDE yang sama-sama memiliki latar belakang dalam teori parser membuat sendiri generator parser yang dapat menganalisis tata bahasa untuk mencari ambiguitas, dan kemudian memasukkan tata bahasa C # yang diusulkan untuk pemahaman kueri ke dalamnya. ; melakukan hal itu menemukan banyak kasus di mana kueri menjadi ambigu.
Atau, ketika kami melakukan inferensi tipe lanjutan pada lambda di C # 3.0 kami menulis proposal kami dan kemudian mengirimkannya ke Microsoft Research di Cambridge di mana tim bahasa di sana cukup baik untuk membuat bukti formal bahwa proposal inferensi tipe itu secara teoritis terdengar.
Apakah ada ambiguitas dalam C # hari ini?
Tentu.
G(F<A, B>(0))
Dalam C # 1 jelas apa artinya. Itu sama dengan:
G( (F<A), (B>0) )
Artinya, ia memanggil G dengan dua argumen yang disebut bools. Dalam C # 2, itu bisa berarti apa yang dimaksud dalam C # 1, tetapi bisa juga berarti "berikan 0 ke metode umum F yang mengambil parameter tipe A dan B, dan kemudian meneruskan hasil dari F ke G". Kami menambahkan heuristik rumit ke parser yang menentukan kasus mana yang mungkin Anda maksudkan.
Demikian pula, pemerannya ambigu bahkan di C # 1.0:
G((T)-x)
Apakah itu "cast -x to T" atau "kurangi x dari T"? Sekali lagi, kami memiliki heuristik yang membuat tebakan bagus.