Czy C # obsługuje kowariancję typu zwracanego?


84

Pracuję z platformą .NET i naprawdę chcę mieć możliwość tworzenia niestandardowego typu strony, z której korzysta cała moja witryna. Problem pojawia się, gdy próbuję uzyskać dostęp do strony z kontrolki. Chcę mieć możliwość zwrócenia strony określonego typu zamiast strony domyślnej. Czy jest na to sposób?

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

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

Odpowiedzi:


170

UPDATE: Ta odpowiedź została napisana w 2011 roku. Po dwóch dekadach ludzi proponujących kowariancję typu zwracanego dla C # wygląda na to, że zostanie ostatecznie zaimplementowana; Jestem raczej zaskoczony. Zapowiedź znajduje się na dole strony https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ ; Jestem pewien, że pojawią się szczegóły.


Wygląda na to, że chcesz kowariancji typu zwracanego. C # nie obsługuje kowariancji typu zwracanego.

Kowariancja typu zwracanego polega na zastąpieniu metody klasy bazowej, która zwraca mniej szczegółowy typ, na taki, który zwraca bardziej szczegółowy typ:

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

Jest to bezpieczne, ponieważ konsumenci Treści za pośrednictwem Obudowy oczekują zwierzęcia, a Akwarium obiecuje nie tylko spełnić ten wymóg, ale co więcej, złożyć bardziej rygorystyczną obietnicę: zwierzę jest zawsze rybą.

Ten rodzaj kowariancji nie jest obsługiwany w języku C # i jest mało prawdopodobne, aby kiedykolwiek był obsługiwany. Nie jest obsługiwany przez CLR. (Jest obsługiwany przez C ++ i przez implementację C ++ / CLI w CLR; robi to poprzez generowanie magicznych metod pomocniczych, które sugeruję poniżej.)

(Niektóre języki obsługują również kontrawariancję typu parametru formalnego - można przesłonić metodę pobierającą Fish metodą pobierającą Animal. Ponownie, kontrakt jest spełniony; klasa bazowa wymaga obsługi dowolnego Fisha, a pochodna class obiecuje nie tylko obsługiwać ryby, ale także dowolne zwierzęta. Podobnie, C # i CLR nie obsługują formalnej kontrawariancji typu parametru).

Aby obejść to ograniczenie, wykonaj następujące czynności:

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() { ... }
}

Teraz zyskujesz zarówno zalety zastępowania metody wirtualnej, jak i silniejsze pisanie podczas korzystania z czegoś w rodzaju Aquarium typu czasu kompilacji.


3
Idealny! Zapomniałem o ukrywaniu się!
Kyle Sletten

3
dobra odpowiedź, jak zawsze. Przez chwilę brakowało nam tych odpowiedzi.
Dhananjay,

4
Czy istnieje uzasadnienie, aby nie wspierać kowariancji zwrotów i kontrawariancji parametrów, które są dostępne do przeczytania w dowolnym miejscu?
porusza się

26
@EricLippert Będąc fanem języka C #, zawsze jestem ciekawy „za kulisami”, więc muszę tylko zapytać: czy kiedykolwiek rozważano kowariancję typu zwracanego dla C # i uznano, że ma zbyt niski zwrot z inwestycji? Rozumiem ograniczenia czasowe i zestawienia błędów, ale z Twojego stwierdzenia „mało prawdopodobne, aby kiedykolwiek był obsługiwany”, wygląda na to, że zespół projektowy faktycznie wolałby NIE mieć tego w języku. Z drugiej strony Java wprowadziła tę funkcję języka w Javie 5, więc zastanawiam się, jaka jest różnica w większych, nadrzędnych zasadach rządzących procesem projektowania języka.
Cristian Diaconescu,

7
@ Cyral: poprawne; jest na liście w segmencie „umiarkowanego zainteresowania” - czyli od dawna! To może się zdarzyć, ale jestem dość sceptyczny.
Eric Lippert

8

Dzięki interfejsom obejrzałem to, jawnie implementując interfejs:

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

2

Umieszczenie tego w obiekcie MyControl zadziałałoby:

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

Nie możesz zastąpić właściwości, ponieważ zwraca ona inny typ ... ale możesz ją przedefiniować.

W tym przykładzie nie potrzebujesz kowariancji, ponieważ jest ona stosunkowo prosta. Jedyne, co robisz, to dziedziczenie obiektu podstawowego Pagez MyPage. Wszystko Control, co chcesz zwrócić, MyPagezamiast tego, Pagemusi ponownie zdefiniować PagewłaściwośćControl


1

Tak, obsługuje kowariancję, ale zależy to od dokładnej rzeczy, którą próbujesz osiągnąć.

Często też używam generycznych do rzeczy, co oznacza, że ​​kiedy robisz coś takiego:

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

}

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

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

I nie „gub” swoich typów.


1

Jest to funkcja nadchodzącej wersji C # 9.0 (.Net 5), z której można teraz pobrać wersję zapoznawczą.

Poniższy kod teraz buduje pomyślnie (bez podania: 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

Nie próbowałem tego, ale czy to nie działa?

YourPageType myPage = (YourPageType)yourControl.Page;

1
Tak, to działa. Chcę tylko spróbować uniknąć zarzucania wszędzie.
Kyle Sletten

0

Tak. Można to zrobić na wiele sposobów, a to tylko jedna opcja:

Możesz sprawić, aby Twoja strona zaimplementowała jakiś niestandardowy interfejs, który ujawnia metodę o nazwie „GetContext” lub coś w tym rodzaju i zwraca określone informacje. Wtedy Twoja kontrola może po prostu zażądać strony i przesłać:

var myContextPage = this.Page as IMyContextGetter;

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

Następnie możesz użyć tego kontekstu, jak chcesz.


0

Możesz uzyskać dostęp do swojej strony z dowolnej kontrolki, przechodząc w górę drzewa nadrzędnego. To jest

myParent = this;

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

* Nie skompilowano ani nie testowano.

Lub pobierz stronę nadrzędną w bieżącym kontekście (w zależności od wersji).


W takim razie lubię to robić: tworzę interfejs z funkcjami, których chcę użyć w kontrolce (na przykład IHostingPage)

Następnie rzutuję stronę nadrzędną „IHostingPage host = (IHostingPage) Parent;” i jestem gotowy do wywołania funkcji na stronie, której potrzebuję z mojej kontroli.


0

Zrobię to w ten sposób:

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; } }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.