'Ref' ve 'out' polimorfizmi neden desteklemiyor?


124

Aşağıdakileri yapın:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= compile-time error: 
                     // "The 'ref' argument doesn't match the parameter type"
    }

    void Foo(A a) {}

    void Foo2(ref A a) {}  
}

Yukarıdaki derleme zamanı hatası neden ortaya çıkıyor? Bu hem refve hem de outargümanlarda olur .

Yanıtlar:


169

=============

GÜNCELLEME: Bu yanıtı bu blog girişinin temeli olarak kullandım:

Ref ve out parametreleri neden tür değişikliğine izin vermez?

Bu konu hakkında daha fazla yorum için blog sayfasına bakın. Bu mükemmel soru için teşekkürler.

=============

Diyelim ki sınıfları varsayalım Animal, Mammal, Reptile, Giraffe, Turtleve Tiger, bariz sınıflara ilişkilerle.

Şimdi bir yönteminiz olduğunu varsayalım void M(ref Mammal m). Mhem okuyabilir hem de yazabilir m.


Bir değişken türüne geçebilir Animalmisiniz M?

Hayır. Bu değişken a içerebilir Turtle, ancak Msadece Memelileri içerdiğini varsayacaktır. A Turtle, a değildir Mammal.

Sonuç 1 : refparametreler "daha büyük" yapılamaz. (Memelilerden daha fazla hayvan var, bu nedenle değişken daha fazla şey içerebileceği için "büyüyor".)


Bir değişken türüne geçebilir Giraffemisiniz M?

Hayır M, yazabilir mve içine Myazmak isteyebilir . Şimdi , aslında bir tür olan bir değişkeneTigermTigerGiraffe .

Sonuç 2 : refparametreler "daha küçük" yapılamaz.


Şimdi düşünün N(out Mammal n).

Bir değişken türüne geçebilir Giraffemisiniz N?

Hayır. A Nyazabilir nve Nyazmak isteyebilir Tiger.

Sonuç 3 : outparametreler "daha küçük" yapılamaz.


Eğer türünde bir değişken geçirebilirsiniz Animaliçin N?

Hmm.

Peki neden olmasın? Nokuyamıyor n, sadece yazabiliyor, değil mi? TigerBir değişken türüne bir yazarsınız Animalve hazırsınız, değil mi?

Yanlış. Kural " Nsadece yazabilir n" değildir.

Kurallar kısaca:

1) normal Nşekilde ndönmeden önce Nyazmalıdır. (EğerN Atarsa, tüm bahisler kapalıdır.)

2) bir Nşey nokumadan önce bir şeyler yazmalın .

Bu, şu olaylar dizisine izin verir:

  • Bir alanı bildirmek xÇeşidiAnimal .
  • Parametresine xbir outparametre olarak iletin N.
  • Nbir takma ad olan Tigeriçine yazarnx .
  • Başka bir iş parçacığında, biri Turtleiçine yazar x.
  • Na'nın içeriğini okumaya çalışır nve bir Turtletür değişkeni olduğunu düşündüğü şeyde bir keşfeder Mammal.

Açıkça bunu yasadışı yapmak istiyoruz.

Sonuç 4 : outparametreler "daha büyük" yapılamaz.


Nihai sonuç : Ne refne de outparametreler türlerini değiştirebilir. Bunun aksini yapmak, doğrulanabilir tip güvenliğini kırmaktır.

Temel tip teorisindeki bu konular ilginizi çekiyorsa, kovaryans ve kontraveransın C # 4.0'da nasıl çalıştığına dair serimi okumayı düşünün .


6
+1. Sorunları açıkça gösteren gerçek dünya standartlarında örnekler kullanan harika bir açıklama (yani - A, B ve C ile açıklama neden işe yaramadığını göstermeyi zorlaştırır).
Grant Wagner

4
Bu düşünce sürecini okurken kendimi alçakgönüllü hissediyorum. Kitaplara geri dönsem iyi olur!
Scott McKenzie

Bu durumda, Özet sınıf değişkenini argüman olarak kullanamayız ve türetilmiş sınıf nesnesini iletemeyiz !!
Prashant Cholachagudda

Yine de outparametreler neden "daha büyük" yapılamıyor? Tanımladığınız sıra, yalnızca outparametre değişkenine değil, herhangi bir değişkene uygulanabilir . Ve ayrıca okuyucunun argüman değerini Mammalona erişmeye çalışmadan önce atması gerekiyor Mammalve tabii ki düşünceli değilse başarısız olabilir
astef

29

Çünkü her iki durumda da ref / out parametresine değer atayabilmeniz gerekir.

B'yi Foo2 yöntemine referans olarak geçirmeye çalışırsanız ve Foo2'de a = new A () olduğunu varsaymaya çalışırsanız, bu geçersiz olacaktır.
Yazamamanla aynı sebep:

B b = new A();

+1 Doğrudan konuya ve nedenini çok iyi açıklıyor.
Rui Craveiro

10

Klasik OOP problemi olan kovaryans (ve kontravarlık) ile mücadele ediyorsunuz , wikipedia'ya bakın : Bu gerçek sezgisel beklentilere meydan okursa da, türetilmiş sınıfların değişken (atanabilir) argümanlar (ve Liskov'un ilkesine saygı göstermeye devam ederken aynı sebepten ötürü öğeleri atanabilir olan konteynerler . Bunun neden böyle olduğu mevcut cevaplarda taslak haline getirilmiş ve bu wiki makalelerinde ve bunlardan gelen bağlantılarda daha derinlemesine incelenmiştir.

Geleneksel olarak statik olarak güvenli kalırken bunu yapan OOP dilleri "aldatmadır" (gizli dinamik tip kontrolleri ekleme veya kontrol etmek için TÜM kaynakların derleme zamanı incelemesini gerektirme); temel seçim şudur: ya bu kovaryanstan vazgeçin ve uygulayıcıların şaşkınlığını kabul edin (C # burada yaptığı gibi) ya da dinamik bir yazım yaklaşımına geçin (ilk OOP dili, Smalltalk, yaptığı gibi) ya da değişmez (tekli) atama) veriler, işlevsel dillerin yaptığı gibi (değişmezlik altında, kovaryansı destekleyebilir ve ayrıca, değişken veri dünyasında Kare alt sınıf Dikdörtgene sahip olamayacağınız gerçeği gibi diğer ilgili bulmacalardan kaçınabilirsiniz).


4

Düşünmek:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

Tip güvenliğini ihlal eder


Daha çok, oradaki sorun olan varlığa bağlı olarak anlaşılması güç "b" türü.

Sanırım 6. satırda => B b = null demek istediniz;
Alejandro Miralles

@amiralles - evet, bu vartamamen yanlıştı. Sabit.
Henk Holterman

4

Diğer yanıtlar bu davranışın arkasındaki mantığı kısa ve öz bir şekilde açıklasa da, bu tür bir şeyi gerçekten yapmanız gerekiyorsa, Foo2'yi genel bir yöntem haline getirerek benzer işlevselliği elde edebileceğinizi belirtmekte fayda var:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}

2

Vererek Çünkü Foo2bir ref Bhatalı biçimlendirilmiş bir nesne neden olacaktır, çünkü Foo2sadece doldurmak bilen Abir kısmını B.


0

Bu derleyici, niyetinizin ne olduğunu bildiğinizden emin olmak için nesneyi açıkça yayınlamanızı istediğini söylemiyor mu?

Foo2(ref (A)b)

Bunu yapamıyorum, "Bir ref veya out argümanı atanabilir bir değişken olmalıdır"

0

Güvenlik açısından mantıklıdır, ancak referansla iletilen polimoprhic nesnelerin yasal kullanımları olduğundan derleyici bir hata yerine bir uyarı verdiyse bunu tercih ederdim. Örneğin

class Derp : interfaceX
{
   int somevalue=0; //specified that this class contains somevalue by interfaceX
   public Derp(int val)
    {
    somevalue = val;
    }

}


void Foo(ref object obj){
    int result = (interfaceX)obj.somevalue;
    //do stuff to result variable... in my case data access
    obj = Activator.CreateInstance(obj.GetType(), result);
}

main()
{
   Derp x = new Derp();
   Foo(ref Derp);
}

Bu derlenmeyecek ama işe yarayacak mı?


0

Türleriniz için pratik örnekler kullanırsanız, göreceksiniz:

SqlConnection connection = new SqlConnection();
Foo(ref connection);

Ve şimdi atayı alan işleviniz var ( yani Object ):

void Foo2(ref Object connection) { }

Bunda ne yanlış olabilir ki?

void Foo2(ref Object connection)
{
   connection = new Bitmap();
}

Az önce Bitmap,SqlConnection .

Bu iyi değil.


Başkalarıyla tekrar deneyin:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

void Foo2(ref DbConnection connection)
{
    conn = new OracleConnection();
}

Bir dolma OracleConnection, aramalarınızdan aşırı top SqlConnection.


0

Benim durumumda işlevim bir nesneyi kabul etti ve hiçbir şey gönderemedim, bu yüzden basitçe yaptım

object bla = myVar;
Foo(ref bla);

Ve bu işe yarar

My Foo VB.NET'te ve içerideki yazıyı kontrol ediyor ve çok fazla mantık yapıyor

Cevabım yineleniyor ancak diğerleri çok uzunsa özür dilerim

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.