Neden yeni anahtar kelimeye ihtiyacımız var ve neden varsayılan davranış gizlemek ve geçersiz kılmak değil?


81

Bu blog gönderisine bakıyordum ve aşağıdaki sorularım vardı:

  • Neden newanahtar kelimeye ihtiyacımız var, sadece temel sınıf yönteminin gizlendiğini belirtmek için mi? Demek istediğim, buna neden ihtiyacımız var? overrideAnahtar kelimeyi kullanmazsak , temel sınıf yöntemini gizlemiyor muyuz?
  • Neden C #'daki varsayılan gizlemek ve geçersiz kılmak değil? Tasarımcılar neden bu şekilde uyguladılar?

6
Biraz daha ilginç bir soru (bana göre), saklanmanın neden yasal olduğudur. Genellikle bir hatadır (dolayısıyla derleyici uyarısıdır), nadiren istenir ve her zaman en iyi ihtimalle sorunludur.
Konrad Rudolph

Yanıtlar:


127

Güzel sorular. Yeniden ifade edeyim.

Bir yöntemi başka bir yöntemle gizlemek neden yasaldır?

Bu soruya bir örnekle cevap vereyim. CLR v1'den bir arayüzünüz var:

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Süper. Şimdi CLR v2'de jeneriklere sahipsiniz ve "dostum, v1'de jenerik olsaydı bunu genel bir arayüz yapardım. Ama yapmadım. Şimdi onunla uyumlu bir şey yapmalıyım ki bu genel IEnumerable bekleyen kodla geriye dönük uyumluluğu kaybetmeden jeneriklerin avantajlarından yararlanıyorum. "

interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> .... uh oh

GetEnumerator yöntemini ne olarak adlandıracaksınız IEnumerable<T>? Seni hatırladım genel olmayan temel arayüzde GetEnumerator'ı gizlemesini istiyorsunuz . Sen asla bir geriye-compat durumda açıkça olmadıkça o şey çağrılmasını isteriz.

Tek başına bu, yöntemi gizlemeyi haklı çıkarır. Yöntem gizlemenin gerekçeleri hakkında daha fazla düşünce için bkz. için konuyla ilgili .

Neden "yeni" olmadan saklanmak bir uyarıya neden olur?

Çünkü bir şeyi sakladığınızı ve yanlışlıkla yapıyor olabileceğinizi dikkatinize sunmak istiyoruz. Unutmayın, türetilmiş sınıfınızı düzenlemek yerine başka biri tarafından temel sınıfa yapılan bir düzenleme nedeniyle yanlışlıkla bir şeyi gizliyor olabilirsiniz.

Neden hata yerine "yeni" bir uyarı olmadan saklanıyor?

Aynı sebep. Bir temel sınıfın yeni bir versiyonunu aldığınız için yanlışlıkla bir şey saklıyor olabilirsiniz. Bu her zaman olur. FooCorp, temel bir B sınıfı oluşturur. BarCorp, bir yöntem Bar ile türetilmiş bir D sınıfı oluşturur, çünkü müşterileri bu yöntemi sever. FooCorp bunu görüyor ve diyor ki, bu iyi bir fikir, bu işlevselliği temel sınıfa koyabiliriz. Bunu yaparlar ve Foo.DLL'nin yeni bir sürümünü gönderirler ve BarCorp yeni sürümü aldığında, yöntemlerinin artık temel sınıf yöntemini gizlediği söylense iyi olur.

Bu durumun bir hata değil bir uyarı olmasını istiyoruz çünkü bunu bir hata yapmak, bunun kırılgan temel sınıf probleminin başka bir biçimi olduğu anlamına gelir . C #, birisi temel bir sınıfa bir değişiklik yaptığında, türetilmiş bir sınıfı kullanan kod üzerindeki etkilerin en aza indirilmesi için dikkatlice tasarlanmıştır.

Neden gizleniyor ve varsayılanı geçersiz kılmıyor?

Çünkü sanal geçersiz kılma tehlikelidir . Sanal geçersiz kılma, türetilmiş sınıfların temel sınıfları kullanmak için derlenen kodun davranışını değiştirmesine izin verir. Geçersiz kılma gibi tehlikeli bir şey yapmak , tesadüfen değil, bilinçli ve kasıtlı olarak yaptığınız bir şey olmalıdır .


1
FWIW, "[t] şapka tek başına yöntem gizlemeyi haklı çıkarır" diye katılmıyorum. Bazen geriye dönük uyumluluğu bozmak daha iyidir. Python 3, bunun aslında çok fazla işi mahvetmeden çalıştığını göstermiştir. "Her ne pahasına olursa olsun geriye doğru uyumluluğun" tek makul seçenek olmadığını unutmamak önemlidir. Ve bu sebep başka bir noktaya taşıyor: Bir temel sınıfı değiştirmek her zaman kırılgan olacaktır . Bağımlı bir yapıyı kırmak aslında tercih edilebilir. (Ama asıl sorunun altındaki yorumumdan soruyu cevapladığım için +1.)
Konrad Rudolph

@Konrad: Geriye dönük uyumluluğun zarar verebileceğine katılıyorum. Mono, gelecekteki sürümlerde 1.1 desteğini bırakmaya karar verdi.
simendsjo

1
@Konrad: Dilinizle geriye dönük uyumluluğun kırılması, sizin dilinizi kullanan kişilerin kendi kodlarıyla geriye dönük uyumluluğu bozmalarını kolaylaştırmaktan çok farklıdır. Eric'in örneği 2. durumdur. Ve bu kodu yazan kişi, geriye dönük uyumluluk istediğine karar vermiştir. Dil, mümkünse bu kararı desteklemelidir.
Brian

Tanrı cevap ver Eric. Ancak, varsayılan olarak geçersiz kılmanın kötü olmasının daha spesifik bir nedeni vardır. Bir temel sınıf Tabanı (bir ördek nesnesi döndüren) yeni bir yöntem Duck () tanıtır ise sadece olur yani arasında hiçbir iletişimin (() (Mario ördek yapar) Türetilmiş Türetilmiş bir sınıfta mevcut yöntem Duck aynı imza var sen ve Base yazar), sen do not onlar sadece Duck istediğinde de sınıfların müşterileri Mario ördek yapmak istiyorum.
jyoungdev

Özetle, çoğunlukla değişen temel sınıflar ve arayüzlerle ilgilidir.
jyoungdev

15

Türetilmiş sınıftaki yöntemin önünde new anahtar sözcüğü varsa, yöntem temel sınıftaki yöntemden bağımsız olarak tanımlanır.

Bununla birlikte, yeni veya geçersiz kılmalar belirtmezseniz, ortaya çıkan çıktı, yeniyi belirlediğinizle aynı olur, ancak bir derleyici uyarısı alırsınız (temel sınıf yönteminde bir yöntemi gizlediğinizi bilmiyor olabilirsiniz, veya gerçekten onu geçersiz kılmak isteyip yalnızca anahtar kelimeyi eklemeyi unutmuş olabilirsiniz).

Böylelikle, hatalardan kaçınmanıza ve ne yapmak istediğinizi açıkça göstermenize yardımcı olur ve daha okunabilir kod yapar, böylece kodunuzu kolayca anlayabilirsiniz.


12

Tek etkisinin olduğuna dikkat etmek önemlidir .newBu bağlamda bir Uyarıyı bastırmak etmek önemlidir. Anlambilimde değişiklik yok.

Yani bir cevap şu: newDerleyiciye gizlemenin kasıtlı olduğunu ve uyarıdan kurtulmamız gerektiğini bildirmemiz gerekiyor.

Takip eden soru şudur: Bir yöntemi geçersiz kılmayacaksanız / veremiyorsanız, neden aynı ada sahip başka bir yöntem sunasınız? Çünkü saklanmak özünde bir isim çatışmasıdır. Ve tabii ki çoğu durumda bundan kaçınırsınız.

Kasıtlı olarak saklanmak için düşünebildiğim tek iyi neden, bir adın bir arayüz tarafından size zorlanmasıdır.


6

C # 'da üyeler varsayılan olarak mühürlenmiştir, yani onları geçersiz kılamazsınız ( virtualveya abstractanahtar sözcükleriyle işaretlenmedikçe ) ve bu performans nedenleriyle . Yeni değiştirici açıkça kalıtsal bir üyesini gizlemek için kullanılır.


4
Peki yeni değiştiriciyi gerçekten ne zaman kullanmak istiyorsunuz?
simendsjo

1
Temel sınıftan miras alınan bir üyeyi açıkça gizlemek istediğinizde. Devralınan bir üyeyi gizlemek, üyenin türetilmiş sürümünün temel sınıf sürümünün yerini aldığı anlamına gelir.
Darin Dimitrov

3
Ancak temel sınıfı kullanan herhangi bir yöntem, türetilmiş değil, temel uygulamayı yine de çağıracaktır. Bu benim için tehlikeli bir durum gibi görünüyor.
simendsjo

@simendsjo, @Darin Dimitrov: Yeni anahtar kelimenin gerçekten gerekli olduğu bir kullanım durumu hakkında düşünmekte de zorlanıyorum. Bana öyle geliyor ki her zaman daha iyi yollar var. Ayrıca bir temel sınıf uygulamasını gizlemenin kolayca hatalara yol açan bir tasarım hatası olup olmadığını da merak ediyorum.
Dirk Vollmar

2

Eğer overrideanahtar kelimeyi belirtmeden geçersiz kılma varsayılan ise , sadece isim eşitliği nedeniyle yanlışlıkla üssünüzün bazı yöntemlerini geçersiz kılabilirsiniz.

.Net derleyici stratejisi, bir şeyler ters gidebilirse, sadece güvenli olmak için uyarılar yaymaktır, bu nedenle, bu durumda geçersiz kılma varsayılan ise, her geçersiz kılma yöntemi için bir uyarı olması gerekir - 'uyarı: gerçekten isteyip istemediğinizi kontrol edin geçersiz kılmak için '.


0

Benim tahminim esas olarak çoklu arayüz kalıtımından kaynaklanıyor. Sağduyulu arayüzlerin kullanılması, iki farklı arayüzün aynı yöntem imzasını kullanması çok olasıdır. newAnahtar kelimenin kullanımına izin vermek, iki farklı sınıf oluşturmak zorunda kalmadan, bu farklı uygulamaları tek bir sınıfla oluşturmanıza olanak sağlar.

Güncellendi ... Eric bu örneği nasıl geliştirebileceğim konusunda bana bir fikir verdi.

public interface IAction1
{
    int DoWork();
}
public interface IAction2
{
    string DoWork();
}

public class MyBase : IAction1
{
    public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
    public new string DoWork() { return "Hi"; }
}

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        var ret0 = myClass.DoWork(); //Hi
        var ret1 = ((IAction1)myClass).DoWork(); //0
        var ret2 = ((IAction2)myClass).DoWork(); //Hi
        var ret3 = ((MyBase)myClass).DoWork(); //0
        var ret4 = ((MyClass)myClass).DoWork(); //Hi
    }
}

1
Birden çok arabirim kullanırken, çarpışmalardan kaçınmak için arabirim yöntemlerini açıkça adlandırabilirsiniz. "Bir sınıf için farklı uygulama" derken neyi kastettiğinizi anlamıyorum, her ikisi de aynı imzaya sahip ...
simendsjo

newAyrıca bu noktadan çocukların tümü için miras tabanını değiştirmesine izin anahtar sözcük.
Matthew Whited

-1

Belirtildiği gibi, yöntem / özellik gizleme, bir yöntem veya özellik hakkında başka türlü kolayca değiştirilemeyen şeyleri değiştirmeyi mümkün kılar. Bunun yararlı olabileceği bir durum, miras alınan bir sınıfın temel sınıfta salt okunur olan okuma-yazma özelliklerine sahip olmasına izin vermektir. Örneğin, bir temel sınıfın Değer1-Değer40 adında bir dizi salt okunur özelliğe sahip olduğunu varsayalım (elbette gerçek bir sınıf daha iyi adlar kullanır). Bu sınıfın mühürlü bir soyundan gelen, temel sınıfın bir nesnesini alıp oradan değerleri kopyalayan bir kurucuya sahiptir; sınıf bundan sonra değiştirilmesine izin vermez. Farklı, miras alınabilen bir alt öğe, okunduğunda temel sınıf sürümleriyle aynı şekilde davranan ancak yazıldığında değerlerin yazılmasına izin veren Değer1-Değer40 adlı bir okuma-yazma özelliğini bildirir.

Bu yaklaşımın bir sıkıntısı - belki birisi bana yardım edebilir - aynı sınıftaki belirli bir mülkü hem gölgelemek hem de geçersiz kılmak için bir yol bilmememdir. CLR dillerinden herhangi biri buna izin veriyor mu (vb 2005 kullanıyorum)? Temel sınıf nesnesi ve özellikleri soyut olsaydı yararlı olurdu, ancak bu, bir alt sınıfın gölgelendirebilmesi için önce Value1 - Value40 özelliklerini geçersiz kılmak için bir orta sınıf gerektirir.

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.