Neden C #, yöntemleri varsayılan olarak sanal olmayan olarak uygular?


105

Java'dan farklı olarak, C # neden yöntemleri varsayılan olarak sanal olmayan işlevler olarak ele alıyor? Diğer olası sonuçlardan ziyade bir performans sorunu olması daha olası mıdır?

Anders Hejlsberg'den, mevcut mimarinin ortaya çıkardığı çeşitli avantajlar hakkında bir paragraf okumamı hatırlattım. Peki ya yan etkiler? Varsayılan olarak sanal olmayan yöntemlere sahip olmak gerçekten iyi bir takas mı?


1
Performans nedenlerle söz cevaplar C # derleyicisi çoğunlukla için yöntem çağrıları derler gerçeğini göz ardı callvirt değil diyoruz . Bu nedenle, C # 'da thisreferans boş ise farklı davranan bir yönteme sahip olmak mümkün değildir . Daha fazla bilgi için buraya bakın .
Andy

Doğru! çağrı IL talimatı çoğunlukla statik yöntemlere yapılan çağrılar içindir.
RBT

1
C # 'ın mimarı Anders Hejlsberg'in düşünceleri burada ve burada .
RBT

Yanıtlar:


98

Sınıflar, mirastan yararlanabilmek için tasarlanmalıdır . virtualVarsayılan olarak yöntemlere sahip olmak , sınıftaki her işlevin çıkarılıp başka bir işlevle değiştirilebileceği anlamına gelir ki bu gerçekten iyi bir şey değildir. Hatta birçok insan sınıfların sealedvarsayılan olarak olması gerektiğine inanıyor .

virtualyöntemlerin ayrıca küçük bir performans sonuçları olabilir. Ancak bu, muhtemelen birincil neden değildir.


7
Şahsen, performansla ilgili bu kısımdan şüpheliyim. Sanal işlevler için bile, derleyici, IL kodunda (veya JITting sırasında daha aşağı akışta) callvirtbir basit ile nerede değiştirileceğini çok iyi anlayabilir call. Java HotSpot da aynısını yapar. Cevabın geri kalanı yerinde.
Konrad Rudolph

5
Ben performans ön planda değil katılıyorum ama olabilir de performans sonuçları vardır. Muhtemelen çok küçük. Elbette, bu seçimin nedeni bu değil . Sadece bundan bahsetmek istedim.
Mehrdad Afshari

71
Mühürlü dersler, bebekleri yumruklamak istememe neden oluyor.
mxmissile

6
@mxmissile: ben de, ama iyi API tasarımı zaten zor. Bence varsayılan olarak mühürlenmiş, sahip olduğunuz kod için mükemmel bir anlam ifade ediyor. Çoğu zaman, ekibinizdeki diğer programcı ihtiyacınız olan sınıfı uygularken kalıtımı dikkate almadı ve varsayılan olarak mühürlendiğinde bu mesajı oldukça iyi iletirdi.
Roman Starkov

49
Birçok insan da psikopattır, onları dinlememiz gerektiği anlamına gelmez. Sanal, C # 'da varsayılan olmalıdır, kod, uygulamaları değiştirmek veya tamamen yeniden yazmak zorunda kalmadan genişletilebilir olmalıdır. API'lerini aşırı koruyan insanlar genellikle, onu kötüye kullanan birinin senaryosundan çok daha kötü olan, ölü veya yarı kullanılmış API'lerle sonuçlanır.
Chris Nicola

89

Burada, varsayılan olarak sanal olmayanın işleri yapmanın doğru yolu olduğu konusunda böyle bir fikir birliği olduğuna şaşırıyorum. Çitin öteki tarafına - pragmatik olduğunu düşünüyorum - aşağı ineceğim.

Gerekçelerin çoğu bana eski "Size güç verirsek kendinize zarar verebilirsiniz" argümanı gibi okunuyor. Programcılardan mı ?!

Bana öyle geliyor ki, kütüphanesini miras ve / veya genişletilebilirlik için tasarlamak için yeterince bilmeyen (veya yeterli zamanı olan) kodlayıcı, tam olarak düzeltmem veya ayarlamam gereken kütüphaneyi üreten kodlayıcıdır - tam olarak geçersiz kılma yeteneğinin en yararlı olacağı kütüphane.

Çirkin, çaresiz bir çözüm kodu yazmak zorunda kaldığım (veya kullanımdan vazgeçip kendi alternatif çözümümü yuvarlamak için) kaç kez geçersiz kılamıyorum çünkü ısırıldığımdan çok daha ağır basmaktadır ( örneğin Java'da) tasarımcının yapabileceğimi düşünmemiş olabileceği yerleri geçersiz kılarak.

Varsayılan olarak sanal olmaması hayatımı zorlaştırıyor.

GÜNCELLEME: Soruyu gerçekten cevaplamadığım [oldukça doğru bir şekilde] belirtildi. Yani - ve geç kaldığım için özür dilerim ...

"C #, yöntemleri varsayılan olarak sanal olmayan olarak uygular, çünkü programlara programcılardan daha fazla değer veren kötü bir karar verildi" gibi özlü bir şey yazabilmek istedim. (Sanırım bu, bu soruya verilecek diğer yanıtlardan bazılarına dayanarak - performans (erken optimizasyon, herhangi biri?) Veya sınıfların davranışını garanti altına alma gibi bir şekilde haklı gösterilebilir.)

Ancak, Stack Overflow'un istediği kesin yanıtı değil, kendi fikrimi ifade ettiğimin farkındayım. Şüphesiz, en üst düzeyde kesin (ama yararsız) cevabın şöyle olduğunu düşündüm:

Varsayılan olarak sanal değillerdir çünkü dil tasarımcılarının vermesi gereken bir karar vardı ve onlar da bunu seçtiler.

Şimdi sanırım bu kararı vermelerinin kesin nedeni, biz asla .... oh, bekle! Bir konuşmanın metni!

Öyleyse, API'leri geçersiz kılma tehlikeleri ve kalıtım için açık bir şekilde tasarlama ihtiyacı hakkındaki cevaplar ve yorumlar doğru yoldadır, ancak hepsinin önemli bir zamansal yönü eksiktir: Anders'in asıl endişesi, bir sınıfın veya API'nin örtülü olarak sürdürülmesiydi. sürümler arasında sözleşme . Ve sanırım aslında, platformun üstündeki kullanıcı kodu değişikliği ile ilgilenmek yerine .Net / C # platformunun kod altında değişmesine izin verme konusunda endişeli. (Ve onun "pragmatik" bakış açısı benimkinin tam tersi çünkü diğer taraftan bakıyor.)

(Ama varsayılan olarak sanal olanı seçip sonra kod tabanında "final" i seçmiş olamazlar mı? Belki de bu tam olarak aynı değildir .. ve Anders açıkça benden daha zeki, bu yüzden yalan söylemesine izin vereceğim.)


2
Daha fazla katılamazdım. Bir Üçüncü Taraf API'sini tüketirken ve bazı davranışları geçersiz kılmak isterken ve bunu yapamamak çok sinir bozucu.
Andy

3
Şevkle katılıyorum. Bir API yayınlayacaksanız (ister şirket içinde ister dış dünyada olsun), kodunuzun kalıtım için tasarlandığından emin olabilirsiniz ve emin olmalısınız. API'yi birçok kişi tarafından kullanılmak üzere yayınlıyorsanız, iyi ve şık yapmalısınız. İyi bir genel tasarım (iyi içerik, açık kullanım senaryoları, testler, dokümantasyon) için gereken çabayla karşılaştırıldığında, miras için tasarım yapmak gerçekten çok kötü değil. Ve eğer yayınlamıyorsanız, varsayılan olarak sanal daha az zaman alır ve sorunlu olan vakaların küçük bir bölümünü her zaman düzeltebilirsiniz.
Gravity

2
Şimdi, Visual Studio'da tüm yöntemleri / özellikleri otomatik olarak etiketlemek için yalnızca bir düzenleyici özelliği olsaydı virtual... Visual Studio eklentisi herkes?
kevinarpe

1
Size tüm kalbimle katılıyorum ama bu tam olarak bir cevap değil.
Chris Morgan

2
Bağımlılık enjeksiyonu, alaycı çerçeveler ve ORM'ler gibi modern geliştirme uygulamaları göz önüne alındığında, C # tasarımcılarımızın işareti biraz kaçırdığı açık görünüyor. Varsayılan olarak bir özelliği geçersiz kılamadığınızda bir bağımlılığı test etmek zorunda kalmak çok sinir bozucu.
Jeremy Holovacs

17

Çünkü bir yöntemin geçersiz kılınabileceğini ve bunun için tasarlanmadığını unutmak çok kolaydır. C #, onu sanal yapmadan önce düşünmenizi sağlar. Bunun harika bir tasarım kararı olduğunu düşünüyorum. Hatta bazı insanlar (Jon Skeet gibi) sınıfların varsayılan olarak mühürlenmesi gerektiğini bile söyledi.


12

Başkalarının söylediklerini özetlemek gerekirse, birkaç neden var:

1- C # 'da, doğrudan C ++' dan gelen sözdizimi ve anlambilimde birçok şey vardır. C ++ 'da varsayılan olarak sanal olmayan yöntemlerin C #' ı etkilediği gerçeği.

2- Her yöntemin varsayılan olarak sanal olması bir performans sorunudur çünkü her yöntem çağrısı nesnenin Sanal Tablosunu kullanmalıdır. Dahası, bu Just-In-Time derleyicisinin yöntemleri satır içi yapma ve diğer optimizasyon türlerini gerçekleştirme becerisini büyük ölçüde sınırlar.

3- En önemlisi, eğer yöntemler varsayılan olarak sanal değilse, sınıflarınızın davranışını garanti edebilirsiniz. Java'da olduğu gibi varsayılan olarak sanal olduklarında, basit bir alıcı yönteminin amaçlandığı gibi yapacağını garanti edemezsiniz, çünkü türetilmiş bir sınıfta herhangi bir şey yapmak geçersiz kılınabilir (tabii ki yapabilir ve yapmalısınız) yöntem ve / veya sınıf finali).

Zifre'nin bahsettiği gibi, C # dilinin neden bir adım daha ileri gitmediğini ve sınıfları varsayılan olarak mühürlemediğini merak edebilirsiniz. Bu, çok ilginç bir konu olan uygulama kalıtımının sorunları hakkındaki tüm tartışmanın bir parçası.


1
Ben de varsayılan olarak mühürlenmiş sınıfları tercih ederdim. O zaman tutarlı olur.
Andy

9

C #, C ++ 'dan (ve daha fazlasından) etkilenir. C ++, varsayılan olarak dinamik dağıtımı (sanal işlevler) etkinleştirmez. Bunun için bir (iyi?) Argüman şu sorudur: "Bir sınıf hiearchy'nin üyeleri olan sınıfları ne sıklıkla uygularsınız?". Varsayılan olarak dinamik gönderimi etkinleştirmekten kaçınmanın bir başka nedeni de bellek ayak izidir. Sanal bir tabloya işaret eden bir sanal işaretçisi (vpointer) olmayan bir sınıf , tabii ki geç bağlamanın etkinleştirildiği karşılık gelen sınıftan daha küçüktür.

Performans sorununun "evet" veya "hayır" demesi o kadar kolay değil. Bunun nedeni, C # 'da bir çalışma zamanı optimizasyonu olan Just In Time (JIT) derlemesidir.

Hakkında başka, benzer bir soru " sanal aramaların hızı .. "


JIT derleyicisi nedeniyle sanal yöntemlerin C # için performans etkilerine sahip olduğundan biraz şüpheliyim. Bu,
JIT'in

1
Aslında, Java tarafından C ++ 'dan daha fazla etkilendiğini düşünüyorum, ki bunu varsayılan olarak yapar.
Mehrdad Afshari

5

Bunun basit nedeni, performans maliyetlerine ek olarak tasarım ve bakım maliyetidir. Bir sanal yöntemin, sanal olmayan bir yönteme kıyasla ek maliyeti vardır çünkü sınıfın tasarımcısı, yöntem başka bir sınıf tarafından geçersiz kılındığında ne olacağını planlamalıdır. Belirli bir yöntemin iç durumu güncellemesini veya belirli bir davranışa sahip olmasını bekliyorsanız, bu büyük bir etkiye sahiptir. Artık türetilmiş bir sınıf bu davranışı değiştirdiğinde ne olacağını planlamalısınız. Bu durumda güvenilir kod yazmak çok daha zordur.

Sanal olmayan bir yöntemle tam kontrole sahip olursunuz. Yanlış giden her şey orijinal yazarın hatasıdır. Kod hakkında akıl yürütmek çok daha kolaydır.


Bu gerçekten eski bir gönderi ama ilk projesini yazan bir acemi için yazdığım kodun istenmeyen / bilinmeyen sonuçlarından sürekli endişeleniyorum. Sanal olmayan yöntemlerin tamamen BENİM hatam olduğunu bilmek çok rahatlatıcı.
trevorc

2

Tüm C # yöntemleri sanal olsaydı, vtbl çok daha büyük olurdu.

C # nesneleri yalnızca, sınıfın tanımlanmış sanal yöntemlere sahip olması durumunda sanal yöntemlere sahiptir. Tüm nesnelerin bir vtbl eşdeğeri içeren tür bilgilerine sahip olduğu doğrudur, ancak hiçbir sanal yöntem tanımlanmadıysa yalnızca temel Object yöntemleri mevcut olacaktır.

@Tom Hawtin: C ++, C # ve Java'nın C dil ailesinden olduğunu söylemek muhtemelen daha doğru olacaktır :)


1
Vtable'ın daha büyük olması neden bir sorun olsun ki? Sınıf başına yalnızca 1 vtable vardır (örnek başına değil), bu nedenle boyutu çok fazla fark yaratmaz.
Gravity

1

Perl arka planından gelen C #, yeni sınıfın tüm kullanıcılarını potansiyel olarak sahne arkasından haberdar olmaya zorlamadan sanal olmayan bir yöntem aracılığıyla temel sınıfın davranışını genişletmek ve değiştirmek isteyebilecek her geliştiricinin kıyametini mühürlediğini düşünüyorum. detaylar.

List sınıfının Add yöntemini düşünün. Bir geliştirici, belirli bir Liste 'Eklendiğinde' birkaç potansiyel veritabanından birini güncellemek isterse ne olur? Eğer 'Ekle' varsayılan olarak sanal olsaydı, geliştirici, tüm istemci kodunu normal bir 'Liste' yerine bir 'BackedList' olduğunu bilmeye zorlamadan 'Add' yöntemini geçersiz kılan bir 'BackedList' sınıfı geliştirebilirdi. Tüm pratik amaçlar için 'BackedList', müşteri kodundan başka bir 'Liste' olarak görülebilir.

Bu, bir veritabanındaki bir veya daha fazla şema tarafından desteklenen bir veya daha fazla liste bileşenine erişim sağlayabilen büyük bir ana sınıf açısından anlamlıdır. C # yöntemlerinin varsayılan olarak sanal olmadığı göz önüne alındığında, ana sınıf tarafından sağlanan liste basit bir IEnumerable veya ICollection veya hatta bir List örneği olamaz, ancak bunun yerine istemciye yeni sürümün Doğru şemayı güncellemek için 'Ekle' işleminin bir kısmı çağrılır.


Doğru, sanal yöntemler varsayılan olarak işi kolaylaştırır, ancak mantıklı mı? Hayatı kolaylaştırmak için kullanabileceğiniz birçok şey var. Neden sınıflarda sadece halka açık alanlar değil? Davranışını değiştirmek çok kolaydır. Benim görüşüme göre, dildeki her şey kesin bir şekilde kapsüllenmiş, katı ve varsayılan olarak değişmeye dirençli olmalıdır. Yalnızca gerekirse değiştirin. Sadece felsefi abt tasarım kararları. Devamı ..
nawfal

... Güncel konu hakkında konuşmak gerekirse, Miras modeli sadece öyleyse Bkullanılmalıdır A. Bundan Bfarklı bir şey gerektiriyorsa, Ao zaman değildir A. Dilin kendi içinde bir yetenek olarak geçersiz kılmanın bir tasarım hatası olduğuna inanıyorum. Farklı bir Addyönteme ihtiyacınız varsa , koleksiyon sınıfınız bir List. Sahte olduğunu söylemeye çalışmak. Buradaki doğru yaklaşım kompozisyondur (ve numara yapmak değildir). Doğru, tüm çerçeve geçersiz kılma yeteneklerine dayanıyor, ancak bundan hoşlanmıyorum.
nawfal

Sanırım amacınızı anlıyorum, ancak verilen somut örnekte: 'BackedList' sadece 'IList' arayüzünü uygulayabilir ve müşteri sadece arayüzü bilir. sağ? bir şey mi kaçırıyorum ancak ben daha geniş bir noktaya değinmeye çalıştığınızı anlıyorum.
Vetras

0

Kesinlikle bir performans sorunu değil. Sun'ın Java tercümanı, aynı kodu göndermek için ( invokevirtualbayt kodu) kullanır ve HotSpot, tamamen aynı kodu üretir veya üretmez final. Tüm C # nesnelerinin (ancak yapıların değil) sanal yöntemlere sahip olduğuna inanıyorum, bu nedenle her zaman vtbl/ runtime sınıf tanımlamasına ihtiyacınız olacak . C #, "Java benzeri dillerin" bir lehçesidir. C ++ 'dan geldiğini öne sürmek tamamen dürüst değil.

"Miras için tasarlamanız, yoksa onu yasaklamanız" gerektiği fikri var. Bu, hızlı bir şekilde düzeltmeniz gereken ciddi bir iş durumunuz olduğu ana kadar harika bir fikir gibi geliyor. Belki de kontrol etmediğiniz koddan miras alırsınız.


Hotspot, bu optimizasyonu yapmak için çok fazla çaba sarf etmek zorunda kalıyor, çünkü tüm yöntemler varsayılan olarak sanaldır ve bu da büyük bir performans etkisine sahiptir. CoreCLR, çok daha basitken benzer bir performans elde edebiliyor
Yair Halberstadt

@YairHalberstadt Hotspot, çeşitli başka nedenlerle derlenmiş kodu geri alabilmelidir. Kaynağa bakmayalı yıllar oldu, ancak yöntemler arasındaki finalve etkili finalyöntemler arasındaki fark önemsiz. Ayrıca, iki farklı uygulamayla satır içi yöntemler olan bimorfik satır içi işlemi yapabildiğini de belirtmek gerekir.
Tom Hawtin - tackline
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.