Karena Anda bertanya mengapa C # melakukannya dengan cara ini, yang terbaik adalah bertanya kepada pembuat C #. Anders Hejlsberg, arsitek utama untuk C #, menjawab mengapa mereka memilih untuk tidak menggunakan virtual secara default (seperti di Jawa) dalam sebuah wawancara , cuplikan terkait di bawah ini.
Perlu diingat bahwa Java memiliki virtual secara default dengan kata kunci terakhir untuk menandai metode sebagai non-virtual. Masih dua konsep untuk dipelajari, tetapi banyak orang tidak tahu tentang kata kunci akhir atau tidak menggunakan secara proaktif. C # memaksa seseorang untuk menggunakan virtual dan baru / menimpa untuk secara sadar membuat keputusan tersebut.
Ada beberapa alasan. Salah satunya adalah kinerja . Kita dapat mengamati bahwa ketika orang menulis kode di Jawa, mereka lupa menandai metode mereka final. Oleh karena itu, metode tersebut adalah virtual. Karena mereka virtual, mereka tidak berkinerja baik. Hanya ada overhead kinerja yang terkait dengan menjadi metode virtual. Itu satu masalah.
Masalah yang lebih penting adalah versi . Ada dua aliran pemikiran tentang metode virtual. Sekolah pemikiran akademis mengatakan, "Semuanya harus virtual, karena saya mungkin ingin menimpanya suatu hari nanti." Sekolah pemikiran pragmatis, yang berasal dari membangun aplikasi nyata yang berjalan di dunia nyata, mengatakan, "Kita harus benar-benar berhati-hati tentang apa yang kita buat virtual."
Ketika kita membuat sesuatu yang virtual dalam sebuah platform, kita membuat banyak sekali janji tentang bagaimana itu berkembang di masa depan. Untuk metode non-virtual, kami berjanji bahwa ketika Anda memanggil metode ini, x dan y akan terjadi. Ketika kami mempublikasikan metode virtual dalam API, kami tidak hanya berjanji bahwa ketika Anda memanggil metode ini, x dan y akan terjadi. Kami juga berjanji bahwa ketika Anda mengganti metode ini, kami akan menyebutnya dalam urutan khusus ini sehubungan dengan yang lain ini dan negara akan berada di ini dan itu invarian.
Setiap kali Anda mengatakan virtual dalam API, Anda membuat panggilan balik. Sebagai perancang kerangka kerja OS atau API, Anda harus benar-benar berhati-hati tentang hal itu. Anda tidak ingin pengguna mengesampingkan dan mengait pada titik sembarang dalam API, karena Anda tidak dapat selalu membuat janji-janji itu. Dan orang-orang mungkin tidak sepenuhnya memahami janji-janji yang mereka buat ketika mereka membuat sesuatu yang virtual.
Wawancara ini memiliki lebih banyak diskusi tentang bagaimana pengembang berpikir tentang desain warisan kelas, dan bagaimana hal itu menyebabkan keputusan mereka.
Sekarang untuk pertanyaan berikut:
Saya tidak dapat memahami mengapa di dunia ini saya akan menambahkan metode dalam DerivedClass saya dengan nama dan tanda tangan yang sama dengan BaseClass dan mendefinisikan perilaku baru tetapi pada polimorfisme run-time, metode BaseClass akan dipanggil! (yang tidak mengesampingkan tetapi secara logis seharusnya).
Ini akan terjadi ketika kelas turunan ingin menyatakan bahwa ia tidak mematuhi kontrak kelas dasar, tetapi memiliki metode dengan nama yang sama. (Untuk siapa saja yang tidak tahu perbedaan antara new
dan override
di C #, lihat halaman MSDN ini ).
Skenario yang sangat praktis adalah ini:
- Anda membuat API, yang memiliki kelas yang disebut
Vehicle
.
- Saya mulai menggunakan API Anda dan berasal
Vehicle
.
Vehicle
Kelas Anda tidak memiliki metode apa pun PerformEngineCheck()
.
- Di
Car
kelas saya , saya menambahkan metode PerformEngineCheck()
.
- Anda merilis versi baru API dan menambahkan
PerformEngineCheck()
.
- Saya tidak dapat mengganti nama metode saya karena klien saya bergantung pada API saya, dan itu akan menghancurkan mereka.
Jadi ketika saya mengkompilasi ulang terhadap API baru Anda, C # memperingatkan saya tentang masalah ini, misalnya
Jika basis PerformEngineCheck()
tidak virtual
:
app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
Use the new keyword if hiding was intended.
Dan jika basisnya PerformEngineCheck()
adalah virtual
:
app2.cs(15,17): warning CS0114: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
Sekarang, saya harus secara eksplisit membuat keputusan apakah kelas saya benar-benar memperpanjang kontrak kelas dasar, atau jika itu adalah kontrak yang berbeda tetapi kebetulan memiliki nama yang sama.
- Dengan membuatnya
new
, saya tidak merusak klien saya jika fungsionalitas metode dasar berbeda dari metode turunan. Kode apa pun yang dirujuk Vehicle
tidak akan Car.PerformEngineCheck()
dipanggil, tetapi kode yang memiliki referensi Car
akan terus melihat fungsi yang sama dengan yang saya tawarkan PerformEngineCheck()
.
Contoh serupa adalah ketika metode lain di kelas dasar mungkin memanggil PerformEngineCheck()
(terutama dalam versi yang lebih baru), bagaimana seseorang mencegahnya memanggil PerformEngineCheck()
kelas turunan? Di Jawa, keputusan itu ada di tangan kelas dasar, tetapi tidak tahu apa-apa tentang kelas turunannya. Dalam C #, keputusan itu terletak baik pada kelas dasar (melalui virtual
kata kunci), dan pada kelas turunan (melalui new
dan override
kata kunci).
Tentu saja, kesalahan yang dilemparkan oleh kompiler juga menyediakan alat yang bermanfaat bagi programmer untuk tidak secara tiba-tiba membuat kesalahan (mis. Menimpa atau menyediakan fungsionalitas baru tanpa disadari.)
Seperti yang dikatakan Anders, dunia nyata memaksa kita ke dalam masalah-masalah semacam itu yang, jika kita memulai dari awal, kita tidak akan pernah mau masuk ke dalamnya.
EDIT: Menambahkan contoh di mana new
harus digunakan untuk memastikan kompatibilitas antarmuka.
EDIT: Ketika sedang membaca komentar, saya juga menemukan tulisan oleh Eric Lippert (saat itu salah satu anggota komite desain C #) pada contoh skenario lainnya (disebutkan oleh Brian).
BAGIAN 2: Berdasarkan pertanyaan yang diperbarui
Tetapi dalam kasus C # jika SpaceShip tidak menimpa kelas Kendaraan 'mempercepat dan menggunakan baru maka logika kode saya akan rusak. Bukankah itu merugikan?
Siapa yang memutuskan apakah SpaceShip
benar-benar mengesampingkan Vehicle.accelerate()
atau jika itu berbeda? Itu harus menjadi SpaceShip
pengembang. Jadi jika SpaceShip
pengembang memutuskan bahwa mereka tidak menjaga kontrak kelas dasar, maka panggilan Anda untuk Vehicle.accelerate()
tidak boleh pergi ke SpaceShip.accelerate()
, atau haruskah itu? Saat itulah mereka akan menandainya sebagai new
. Namun, jika mereka memutuskan bahwa itu memang mempertahankan kontrak, maka mereka sebenarnya akan menandainya override
. Dalam kedua kasus, kode Anda akan berperilaku benar dengan memanggil metode yang benar berdasarkan kontrak . Bagaimana kode Anda memutuskan apakah SpaceShip.accelerate()
benar-benar mengesampingkan Vehicle.accelerate()
atau jika itu adalah tabrakan nama? (Lihat contoh saya di atas).
Namun, dalam kasus warisan implisit, bahkan jika SpaceShip.accelerate()
tidak menjaga kontrak dari Vehicle.accelerate()
, metode panggilan akan tetap pergi ke SpaceShip.accelerate()
.