C #, dönüş türü kovaryansı destekliyor mu?


88

.NET çerçevesi ile çalışıyorum ve gerçekten tüm web sitemin kullandığı özel bir sayfa türü yapabilmek istiyorum. Sorun sayfaya bir kontrolden erişmeye çalıştığımda ortaya çıkıyor. Varsayılan sayfa yerine belirli türdeki sayfamı döndürebilmek istiyorum. Bunu yapmanın bir yolu var mı?

public class MyPage : Page
{
    // My own logic
}

public class MyControl : Control
{
    public MyPage Page { get; set; }
}

Yanıtlar:


175

GÜNCELLEME: Bu cevap 2011'de yazılmıştır. Yirmi yıllık insan C # için dönüş tipi kovaryans önerdikten sonra, sonunda uygulanacak gibi görünüyor; Ben oldukça şaşırdım. Duyuru için https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ alt kısmına bakın ; Eminim ayrıntılar takip edecek.


Geri dönüş tipi kovaryans istediğiniz gibi geliyor. C #, dönüş türü kovaryansı desteklemez.

Dönüş türü kovaryansı, daha az spesifik bir tür döndüren temel sınıf yöntemini, daha belirli bir tür döndüren bir yöntemle geçersiz kıldığınız yerdir:

abstract class Enclosure
{
    public abstract Animal Contents();
}
class Aquarium : Enclosure
{
    public override Fish Contents() { ... }
}

Bu güvenlidir, çünkü Muhafaza yoluyla İçerik tüketicileri bir Hayvan bekler ve Akvaryum yalnızca bu gereksinimi yerine getirmeyi değil, aynı zamanda daha katı bir söz vermeyi vaat eder: hayvan her zaman bir balıktır.

Bu tür bir kovaryans C # 'da desteklenmez ve muhtemelen desteklenmez. CLR tarafından desteklenmez. (C ++ ve CLR üzerinde C ++ / CLI uygulaması tarafından desteklenir; bunu aşağıda önerdiğim türden sihirli yardımcı yöntemler üreterek yapar.)

(Bazı diller, bir Hayvan alan bir yöntemle bir Balık alan bir yöntemi geçersiz kılabileceğiniz biçimsel parametre türü kontraveriansını da destekler. Yine, sözleşme yerine getirilir; temel sınıf, herhangi bir Balığın ele alınmasını ve türetilmesini gerektirir. sınıf sadece balıkları değil, herhangi bir hayvanı da idare etmeyi vaat ediyor. Benzer şekilde, C # ve CLR, resmi parametre türü kontrolünü desteklemez.)

Bu sınırlamayı aşmanın yolu şuna benzer bir şey yapmaktır:

abstract class Enclosure
{
    protected abstract Animal GetContents();
    public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
    protected override Animal GetContents() { return this.Contents(); }
    public new Fish Contents() { ... }
}

Artık hem sanal bir yöntemi geçersiz kılmanın hem de derleme zamanı türü Aquarium'u kullanırken daha güçlü yazmanın avantajlarını elde edersiniz.


3
Mükemmel! Saklanmayı unuttum!
Kyle Sletten

3
her zamanki gibi iyi cevap. Bir süredir bu cevapları kaçırıyorduk.
Dhananjay

4
Geri dönüş kovaryansını ve param kontraveriği desteklememek için herhangi bir yerde okunabilecek bir mantık var mı?
porges

26
@EricLippert Bir C # grubu olarak, her zaman "sahne arkasını" merak ederim, bu yüzden şunu sormak zorundayım: Geri dönüş tipi kovaryans hiç C # için düşünüldü mü ve çok düşük bir ROI'ye sahip miydi? Zamanı ve bugdet kısıtlamalarını anlıyorum, ancak sizin "asla desteklenmeyecek" ifadenizden, tasarım ekibinin aslında dilde buna sahip OLMAYACAK gibi görünüyor. Çitin diğer tarafında, Java bu dil özelliğini Java 5'te tanıttı, bu yüzden dil tasarım sürecini yöneten daha büyük, kapsayıcı ilkelerdeki farkın ne olduğunu merak ediyorum.
Cristian Diaconescu

7
@Cyral: Doğru; uzun zamandır olduğu gibi, "orta derecede ilgi" kepçesi listesindedir! Olabilir, ancak oldukça şüpheliyim.
Eric Lippert

8

Arayüzlerle, arayüzü açıkça uygulayarak etrafından dolaştım:

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}

2

Bunu MyControl nesnesine yerleştirmek işe yarar:

 public new MyPage Page {get return (MyPage)Page; set;}'

Farklı bir tür döndürdüğü için özelliği geçersiz kılamazsınız ... ancak yeniden tanımlayabilirsiniz.

Görece basit olduğu için bu örnekte kovaryansa ihtiyacınız yok. Yaptığınız tüm baz nesneyi devralıyorsa Pagedan MyPage. Herhangi Controlİade etmek istediğiniz MyPageyerine Pageihtiyaçları yeniden tanımlamak için PagemalıControl


1

Evet, kovaryansı destekler, ancak başarmaya çalıştığınız şeye bağlıdır.

Ayrıca jenerikleri şeyler için çok kullanma eğilimindeyim, bu da şu gibi bir şey yaptığınızda şu anlama geliyor:

class X<T> {
    T doSomething() {
    }

}

class Y : X<Y> {
    Y doSomethingElse() {
    }
}

var Y y = new Y();
y = y.doSomething().doSomethingElse();

Ve tiplerinizi "kaybetmeyin".


1

Bu bir özelliktir yaklaşan C # 9.0 (Net 5) yapabilirsiniz hangi bir önizleme sürümünü indirmek şimdi.

Aşağıdaki kod artık başarılı bir şekilde oluşturuyor (: vermeden error CS0508: 'Tiger.GetFood()': return type must be 'Food' to match overridden member 'Animal.GetFood()')

class Food { }
class Meat : Food { }

abstract class Animal {
    public abstract Food GetFood();
}

class Tiger : Animal {
    public override Meat GetFood() => default;
}

class Program {
    static void Main() => new Tiger();
}

0

Denemedim ama bu işe yaramıyor mu?

YourPageType myPage = (YourPageType)yourControl.Page;

1
Evet öyle. Sadece her yere döküm yapmaktan kaçınmaya çalışmak istiyorum.
Kyle Sletten

0

Evet. Bunu yapmanın birden çok yolu vardır ve bu yalnızca bir seçenektir:

Sayfanızın, "GetContext" adlı bir yöntemi veya başka bir yöntemi ortaya çıkaran bazı özel arabirimler uygulamasını sağlayabilirsiniz ve bu, size özel bilgilerinizi döndürür. Ardından kontrolünüz, sayfayı talep edebilir ve yayınlayabilir:

var myContextPage = this.Page as IMyContextGetter;

if(myContextPage != null)
   var myContext = myContextPage.GetContext();

O zaman bu bağlamı dilediğiniz gibi kullanabilirsiniz.


0

Sayfanıza herhangi bir kontrolden, ana ağaçta yukarı yürüyerek erişebilirsiniz. Yani

myParent = this;

while(myParent.parent != null)
  myParent = myParent.parent;

* Derlemedi veya test etmedi.

Veya mevcut bağlamdaki üst sayfayı alın (sürümünüze bağlıdır).


O zaman yapmaktan hoşlandığım şey şu: Kontrolde kullanmak istediğim işlevlerle bir arayüz oluşturuyorum (örneğin IHostingPage)

Sonra 'IHostingPage host = (IHostingPage) Parent;' üst sayfasını yayınladım. ve ihtiyacım olan sayfadaki işlevi kontrolümden çağırmaya hazırım.


0

Bunu şu şekilde yapacağım:

class R {
    public int A { get; set; }
}

class R1: R {
    public int B { get; set; }
}

class A
{        
    public R X { get; set; }
}

class B : A 
{
    private R1 _x;
    public new R1 X { get => _x; set { ((A)this).X = value; _x = value; } }
}
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.