Mengapa C # mengimplementasikan metode sebagai non-virtual secara default?

105

Tidak seperti Java, mengapa C # memperlakukan metode sebagai fungsi non-virtual secara default? Apakah ini lebih cenderung menjadi masalah kinerja daripada hasil lain yang mungkin?

Saya teringat saat membaca paragraf dari Anders Hejlsberg tentang beberapa keuntungan yang dibawa oleh arsitektur yang ada. Tapi, bagaimana dengan efek sampingnya? Apakah benar-benar merupakan trade-off yang baik untuk memiliki metode non-virtual secara default?

Burcu Dogan
sumber
1
Jawaban yang menyebutkan alasan kinerja mengabaikan fakta bahwa compiler C # kebanyakan mengkompilasi pemanggilan metode ke callvirt dan bukan memanggil . Itulah mengapa di C # tidak mungkin memiliki metode yang berperilaku berbeda jika thisreferensinya null. Lihat di sini untuk info lebih lanjut.
Andy
Benar! panggilan instruksi IL sebagian besar untuk panggilan yang dibuat ke metode statis.
RBT
1
Pemikiran arsitek C # Anders Hejlsberg di sini dan di sini .
RBT

Jawaban:

98

Kelas harus dirancang untuk warisan agar dapat memanfaatkannya. Memiliki metode virtualsecara default berarti bahwa setiap fungsi di kelas dapat dicolokkan dan diganti dengan yang lain, yang sebenarnya bukan hal yang baik. Banyak orang bahkan percaya bahwa kelas seharusnya sealedsecara default.

virtualmetode juga dapat memiliki sedikit implikasi kinerja. Namun, ini sepertinya bukan alasan utama.

Mehrdad Afshari
sumber
7
Secara pribadi, saya meragukan bagian tentang kinerja itu. Bahkan untuk fungsi virtual, kompilator sangat mampu untuk mencari tahu di mana harus mengganti callvirtdengan yang sederhana calldalam kode IL (atau bahkan lebih jauh ke hilir saat JITting). Java HotSpot melakukan hal yang sama. Sisa jawabannya tepat.
Konrad Rudolph
5
Saya setuju kinerja bukanlah yang terdepan, tetapi dapat memiliki implikasi kinerja juga. Mungkin sangat kecil. Namun, tentu saja, itu bukan alasan pilihan ini. Hanya ingin mengatakan itu.
Mehrdad Afshari
71
Kelas tertutup membuatku ingin memukul bayi.
mxmissile
6
@mxmissile: saya juga, tapi desain API yang bagus itu sulit. Saya pikir disegel secara default sangat masuk akal untuk kode yang Anda miliki. Lebih sering daripada tidak, programmer lain di tim Anda tidak mempertimbangkan warisan ketika mereka menerapkan kelas yang Anda butuhkan, dan disegel secara default akan menyampaikan pesan itu dengan cukup baik.
Roman Starkov
49
Banyak orang juga psikopat, bukan berarti kita harus mendengarkan mereka. Virtual harus menjadi default di C #, kode harus dapat diperluas tanpa harus mengubah implementasi atau menulis ulang sepenuhnya. Orang yang terlalu melindungi API mereka sering berakhir dengan API mati atau setengah terpakai yang jauh lebih buruk daripada skenario seseorang yang menyalahgunakannya.
Chris Nicola
89

Saya terkejut bahwa tampaknya ada konsensus di sini bahwa non-virtual-by-default adalah cara yang tepat untuk melakukan sesuatu. Saya akan turun di sisi lain - saya pikir pragmatis - sisi pagar.

Sebagian besar pembenaran bagi saya seperti argumen lama "Jika kami memberi Anda kekuatan, Anda mungkin melukai diri sendiri". Dari programmer ?!

Bagi saya, pembuat kode yang tidak cukup tahu (atau memiliki cukup waktu) untuk mendesain perpustakaan mereka untuk warisan dan / atau ekstensibilitas adalah pembuat kode yang menghasilkan persis perpustakaan yang mungkin harus saya perbaiki atau ubah - persis perpustakaan tempat kemampuan untuk menimpa akan sangat berguna.

Berapa kali saya harus menulis kode penyelesaian yang jelek dan putus asa (atau meninggalkan penggunaan dan menggulung solusi alternatif saya sendiri) karena saya tidak dapat menimpa jauh, jauh melebihi berapa kali saya pernah digigit ( misalnya di Jawa) dengan menimpa di mana desainer mungkin tidak mempertimbangkan saya mungkin.

Non-virtual-by-default membuat hidup saya lebih sulit.

UPDATE: Telah ditunjukkan [dengan benar] bahwa saya sebenarnya tidak menjawab pertanyaan itu. Jadi - dan dengan permintaan maaf karena agak terlambat ....

Saya agak ingin dapat menulis sesuatu yang bernas seperti "C # mengimplementasikan metode sebagai non-virtual secara default karena keputusan yang buruk dibuat yang menghargai program lebih tinggi daripada programmer". (Saya pikir itu bisa dibenarkan berdasarkan beberapa jawaban lain untuk pertanyaan ini - seperti kinerja (pengoptimalan prematur, siapa saja?), Atau menjamin perilaku kelas.)

Namun, saya menyadari bahwa saya hanya akan menyatakan pendapat saya dan bukan jawaban pasti yang diinginkan Stack Overflow. Tentunya, saya pikir, pada tingkat tertinggi jawaban definitif (tetapi tidak membantu) adalah:

Mereka non-virtual secara default karena desainer bahasa harus membuat keputusan dan itulah yang mereka pilih.

Sekarang saya rasa alasan pasti mereka membuat keputusan itu, kami tidak akan pernah .... oh, tunggu! Transkrip percakapan!

Jadi tampaknya jawaban dan komentar di sini tentang bahaya override API dan kebutuhan untuk mendesain secara eksplisit untuk pewarisan berada di jalur yang benar tetapi semuanya kehilangan aspek temporal yang penting: Perhatian utama Anders adalah tentang mempertahankan kelas atau implisit API kontrak lintas versi . Dan saya pikir dia sebenarnya lebih peduli tentang mengizinkan platform .Net / C # untuk berubah di bawah kode daripada peduli tentang perubahan kode pengguna di atas platform. (Dan sudut pandang "pragmatis" nya adalah kebalikan dari saya karena dia melihat dari sisi lain.)

(Tapi tidak bisakah mereka memilih virtual-by-default dan kemudian membumbui "final" melalui basis kode? Mungkin itu tidak persis sama .. dan Anders jelas lebih pintar dari saya jadi saya akan membiarkannya berbohong.)

mwardm
sumber
2
Sangat setuju. Sangat membuat frustrasi saat mengkonsumsi api Pihak Ketiga dan ingin mengganti beberapa perilaku dan tidak mampu melakukannya.
Andy
3
Saya setuju dengan antusias. Jika Anda akan menerbitkan API (baik secara internal di perusahaan atau di dunia luar), Anda benar-benar dapat dan harus memastikan kode Anda dirancang untuk warisan. Anda harus membuat API menjadi bagus dan dipoles jika Anda menerbitkannya untuk digunakan oleh banyak orang. Dibandingkan dengan upaya yang dibutuhkan untuk desain keseluruhan yang baik (konten yang bagus, kasus penggunaan yang jelas, pengujian, dokumentasi), mendesain untuk pewarisan sebenarnya tidak terlalu buruk. Dan jika Anda tidak memublikasikan, virtual secara default tidak memakan waktu, dan Anda selalu dapat memperbaiki sebagian kecil kasus yang bermasalah.
Gravity
2
Sekarang jika hanya ada fitur editor di Visual Studio untuk secara otomatis menandai semua metode / properti sebagai virtual... Visual Studio add-in anyone?
kevinarpe
1
Meskipun saya sepenuhnya setuju dengan Anda, ini bukanlah jawaban yang tepat.
Chris Morgan
2
Mempertimbangkan praktik pengembangan modern seperti injeksi ketergantungan, kerangka kerja tiruan, dan ORM, tampak jelas bahwa desainer C # kami sedikit meleset. Sangat menjengkelkan harus menguji ketergantungan saat Anda tidak dapat mengganti properti secara default.
Jeremy Holovacs
17

Karena terlalu mudah untuk melupakan bahwa suatu metode dapat diganti dan tidak dirancang untuk itu. C # membuat Anda berpikir sebelum menjadikannya virtual. Saya pikir ini adalah keputusan desain yang bagus. Beberapa orang (seperti Jon Skeet) bahkan mengatakan bahwa kelas harus ditutup secara default.

Zifre
sumber
12

Untuk meringkas apa yang orang lain katakan, ada beberapa alasan:

1- Dalam C #, ada banyak hal dalam sintaksis dan semantik yang berasal langsung dari C ++. Fakta bahwa metode di mana tidak-virtual secara default di C ++ mempengaruhi C #.

2- Memiliki setiap metode virtual secara default adalah masalah kinerja karena setiap metode panggilan harus menggunakan Tabel Virtual objek. Selain itu, hal ini sangat membatasi kemampuan compiler Just-In-Time untuk menyebariskan metode dan melakukan jenis pengoptimalan lainnya.

3- Yang terpenting, jika metode tidak virtual secara default, Anda dapat menjamin perilaku kelas Anda. Ketika mereka virtual secara default, seperti di Java, Anda bahkan tidak dapat menjamin bahwa metode pengambil sederhana akan melakukan seperti yang diinginkan karena dapat ditimpa untuk melakukan apa pun di kelas turunan (tentu saja Anda dapat, dan harus, membuat metode dan / atau kelas akhir).

Orang mungkin bertanya-tanya, seperti yang disebutkan Zifre, mengapa bahasa C # tidak melangkah lebih jauh dan membuat kelas disegel secara default. Itu adalah bagian dari seluruh perdebatan tentang masalah implementasi pewarisan, yang merupakan topik yang sangat menarik.

Trillian
sumber
1
Saya lebih suka kelas yang disegel secara default juga. Maka itu akan menjadi konsisten.
Andy
9

C # dipengaruhi oleh C ++ (dan lainnya). C ++ tidak mengaktifkan pengiriman dinamis (fungsi virtual) secara default. Salah satu argumen (bagus?) Untuk ini adalah pertanyaan: "Seberapa sering Anda menerapkan kelas yang merupakan anggota dari kelas hiearchy?". Alasan lain untuk menghindari pengaktifan pengiriman dinamis secara default adalah jejak memori. Kelas tanpa penunjuk virtual (vpointer) yang menunjuk ke tabel virtual , tentu saja lebih kecil dari kelas yang sesuai dengan pengikatan akhir diaktifkan.

Masalah kinerja tidak mudah untuk dikatakan "ya" atau "tidak". Alasan untuk ini adalah kompilasi Just In Time (JIT) yang merupakan pengoptimalan waktu berjalan di C #.

Pertanyaan lain yang serupa tentang " kecepatan panggilan virtual .. "

Schildmeijer
sumber
Saya agak ragu bahwa metode virtual memiliki implikasi kinerja untuk C #, karena compiler JIT. Itulah salah satu area di mana JIT bisa lebih baik daripada kompilasi offline, karena mereka dapat melakukan panggilan fungsi sebaris yang "tidak diketahui" sebelum runtime
David Cournapeau
1
Sebenarnya, menurut saya ini lebih dipengaruhi oleh Java daripada C ++ yang melakukannya secara default.
Mehrdad Afshari
5

Alasan sederhananya adalah biaya desain dan perawatan selain biaya kinerja. Metode virtual memiliki biaya tambahan dibandingkan dengan metode non-virtual karena desainer kelas harus merencanakan apa yang terjadi ketika metode tersebut diganti oleh kelas lain. Ini berdampak besar jika Anda mengharapkan metode tertentu untuk memperbarui status internal atau memiliki perilaku tertentu. Anda sekarang harus merencanakan apa yang terjadi ketika kelas turunan mengubah perilaku itu. Jauh lebih sulit untuk menulis kode yang dapat diandalkan dalam situasi itu.

Dengan metode non-virtual Anda memiliki kendali penuh. Apa pun yang salah adalah kesalahan penulis aslinya. Kode jauh lebih mudah untuk dipikirkan.

JaredPar
sumber
Ini adalah posting yang sangat lama tetapi untuk seorang pemula yang sedang menulis proyek pertamanya, saya terus-menerus mengkhawatirkan konsekuensi yang tidak diinginkan / tidak diketahui dari kode yang saya tulis. Sangat menghibur mengetahui bahwa metode non-virtual sepenuhnya adalah kesalahan SAYA.
trevorc
2

Jika semua metode C # adalah virtual maka vtbl akan jauh lebih besar.

Objek C # hanya memiliki metode virtual jika kelas memiliki metode virtual yang ditentukan. Memang benar bahwa semua objek memiliki informasi tipe yang menyertakan ekivalen vtbl, tetapi jika tidak ada metode virtual yang ditentukan maka hanya metode Objek dasar yang akan ditampilkan.

@ Tom Hawtin: Mungkin lebih akurat untuk mengatakan bahwa C ++, C # dan Java semuanya berasal dari rumpun bahasa C :)

Thomas Bratt
sumber
1
Mengapa menjadi masalah jika vtable menjadi jauh lebih besar? Hanya ada 1 vtabel per kelas (dan bukan per instance), jadi ukurannya tidak membuat banyak perbedaan.
Gravity
1

Berasal dari latar belakang perl saya pikir C # menutup malapetaka dari setiap pengembang yang mungkin ingin memperluas dan memodifikasi perilaku kelas dasar 'melalui metode non virtual tanpa memaksa semua pengguna kelas baru untuk menyadari potensi di balik layar detailnya.

Pertimbangkan metode Tambahkan kelas Daftar. Bagaimana jika pengembang ingin memperbarui salah satu dari beberapa basis data potensial setiap kali Daftar tertentu 'Ditambahkan' ke? Jika 'Tambah' adalah virtual secara default, pengembang dapat mengembangkan kelas 'BackedList' yang menggantikan metode 'Tambah' tanpa memaksa semua kode klien untuk mengetahui bahwa itu adalah 'BackedList' dan bukan 'List' biasa. Untuk semua tujuan praktis 'BackedList' dapat dilihat hanya sebagai 'List' dari kode klien.

Ini masuk akal dari perspektif kelas utama yang besar yang mungkin menyediakan akses ke satu atau lebih komponen daftar yang didukung oleh satu atau beberapa skema dalam database. Mengingat bahwa metode C # tidak virtual secara default, daftar yang disediakan oleh kelas utama tidak dapat berupa IEnumerable atau ICollection sederhana atau bahkan contoh List tetapi harus diiklankan ke klien sebagai 'BackedList' untuk memastikan bahwa versi baru operasi 'Tambah' dipanggil untuk memperbarui skema yang benar.

Travis
sumber
Benar, metode virtual secara default membuat pekerjaan lebih mudah, tetapi apakah itu masuk akal? Ada banyak hal yang dapat Anda terapkan untuk membuat hidup lebih mudah. Mengapa tidak hanya bidang umum di kelas? Mengubah perilakunya terlalu mudah. Menurut pendapat saya, segala sesuatu dalam bahasa harus dikemas secara ketat, kaku, dan tangguh untuk berubah secara default. Ubah hanya jika diperlukan. Hanya menjadi keputusan desain filosofis kecil. Lanjutan ..
nawfal
... Untuk berbicara tentang topik saat ini, model Warisan harus digunakan hanya jika Bada A. Jika Bmembutuhkan sesuatu yang berbeda dari Amaka tidak A. Saya percaya mengesampingkan sebagai kemampuan dalam bahasa itu sendiri adalah cacat desain. Jika Anda membutuhkan Addmetode yang berbeda , maka kelas koleksi Anda bukan List. Mencoba mengatakan itu palsu. Pendekatan yang tepat di sini adalah komposisi (dan bukan pemalsuan). Benar, seluruh kerangka dibangun di atas kemampuan utama, tapi saya tidak menyukainya.
nawfal
Saya rasa saya mengerti maksud Anda, tetapi pada contoh konkret yang diberikan: 'BackedList' hanya dapat mengimplementasikan antarmuka 'IList' dan klien hanya tahu tentang antarmuka. Baik? apakah saya melewatkan sesuatu? namun saya memahami poin yang lebih luas yang Anda coba buat.
Vetras
0

Ini tentu bukan masalah kinerja. Interpreter Java Sun menggunakan kode yang sama untuk mengirimkan ( invokevirtualbytecode) dan HotSpot menghasilkan kode yang sama persis finalatau tidak. Saya percaya semua objek C # (tetapi bukan struct) memiliki metode virtual, jadi Anda akan selalu memerlukan vtblidentifikasi kelas / runtime. C # adalah dialek dari "bahasa mirip Java". Untuk menyarankan itu berasal dari C ++ tidak sepenuhnya jujur.

Ada gagasan bahwa Anda harus "merancang untuk warisan atau melarangnya". Kedengarannya seperti ide bagus hingga saat Anda memiliki kasus bisnis yang parah untuk diselesaikan dengan cepat. Mungkin mewarisi dari kode yang tidak Anda kontrol.

Tom Hawtin - tackline
sumber
Hotspot terpaksa berusaha keras untuk melakukan pengoptimalan itu, justru karena semua metode virtual secara default, yang memiliki dampak kinerja yang sangat besar. CoreCLR mampu mencapai kinerja serupa sementara jauh lebih sederhana
Yair Halberstadt
@YairHalberstadt Hotspot harus dapat mencadangkan kode yang dikompilasi karena berbagai alasan lain. Sudah bertahun-tahun sejak saya melihat sumbernya, tetapi perbedaan antara finaldan finalmetode efektif itu sepele. Perlu juga dicatat bahwa ia dapat melakukan bimorphic inlining, yaitu metode inline dengan dua implementasi berbeda.
Tom Hawtin - tackline