Unterstützt C # die Kovarianz vom Rückgabetyp?


84

Ich arbeite mit dem .NET Framework und möchte wirklich einen benutzerdefinierten Seitentyp erstellen können, den alle meine Websites verwenden. Das Problem tritt auf, wenn ich versuche, über ein Steuerelement auf die Seite zuzugreifen. Ich möchte in der Lage sein, meinen bestimmten Seitentyp anstelle der Standardseite zurückzugeben. Gibt es eine Möglichkeit, dies zu tun?

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

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

Antworten:


167

UPDATE: Diese Antwort wurde 2011 geschrieben. Nach zwei Jahrzehnten, in denen Leute eine Kovarianz vom Rückgabetyp für C # vorgeschlagen haben, sieht es so aus, als würde sie endlich implementiert. Ich bin ziemlich überrascht. Die Ankündigung finden Sie unten unter https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ . Ich bin sicher, Details werden folgen.


Es hört sich so an, als ob Sie eine Kovarianz vom Rückgabetyp wünschen. C # unterstützt keine Kovarianz vom Rückgabetyp.

Bei der Kovarianz des Rückgabetyps überschreiben Sie eine Basisklassenmethode, die einen weniger spezifischen Typ zurückgibt, durch eine Methode, die einen spezifischeren Typ zurückgibt:

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

Dies ist sicher, da Verbraucher von Inhalten über Beilage ein Tier erwarten und Aquarium verspricht, diese Anforderung nicht nur zu erfüllen, sondern darüber hinaus ein strengeres Versprechen abzugeben: dass das Tier immer ein Fisch ist.

Diese Art der Kovarianz wird in C # nicht unterstützt und wird wahrscheinlich nie unterstützt. Es wird von der CLR nicht unterstützt. (Es wird von C ++ und von der C ++ / CLI-Implementierung in der CLR unterstützt. Dazu werden magische Hilfsmethoden der unten vorgeschlagenen Art generiert.)

(Einige Sprachen unterstützen auch die Kontravarianz des formalen Parametertyps - Sie können eine Methode, die einen Fisch nimmt, mit einer Methode überschreiben, die ein Tier nimmt. Auch hier ist der Vertrag erfüllt. Die Basisklasse erfordert, dass jeder Fisch behandelt und der abgeleitete Die Klasse verspricht, nicht nur mit Fischen, sondern mit jedem Tier umzugehen. Ebenso unterstützen C # und die CLR keine Kontravarianz vom formalen Parametertyp.)

Sie können diese Einschränkung umgehen, indem Sie Folgendes tun:

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

Jetzt haben Sie sowohl die Vorteile, eine virtuelle Methode zu überschreiben, als auch eine stärkere Eingabe, wenn Sie etwas vom Typ Aquarium zur Kompilierungszeit verwenden.


3
Perfekt! Ich habe vergessen mich zu verstecken!
Kyle Sletten

3
gute Antwort wie immer. Wir haben diese Antworten für eine Weile vermisst.
Dhananjay

4
Gibt es eine Begründung dafür, dass Return-Kovarianz und Param-Contravarianz nicht unterstützt werden, die irgendwo gelesen werden können?
porges

26
@EricLippert Als C # -Gruppe bin ich immer neugierig auf die "Backstage", daher muss ich nur fragen: Wurde die Kovarianz des Rückgabetyps jemals für C # in Betracht gezogen und als zu niedrig eingestuft? Ich verstehe Zeit- und Bugdet-Einschränkungen, aber aus Ihrer Aussage "Es ist unwahrscheinlich, dass sie jemals unterstützt werden" geht hervor, dass das Designteam dies eigentlich lieber NICHT in der Sprache haben würde. Auf der anderen Seite des Zauns hat Java diese Sprachfunktion in Java 5 eingeführt. Ich frage mich daher, was der Unterschied in den umfassenderen Prinzipien ist, die den Sprachentwurfsprozess bestimmen.
Cristian Diaconescu

7
@ Cyral: Richtig; es steht auf der Liste im Eimer "mäßiges Interesse" - dort ist es schon lange! Es könnte passieren, aber ich bin ziemlich skeptisch.
Eric Lippert

7

Mit Schnittstellen habe ich es umgangen, indem ich die Schnittstelle explizit implementiert habe:

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

2

Das Einfügen in das MyControl-Objekt würde funktionieren:

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

Sie können die Eigenschaft nicht überschreiben, da sie einen anderen Typ zurückgibt. Sie können sie jedoch neu definieren.

In diesem Beispiel benötigen Sie keine Kovarianz, da dies relativ einfach ist. Sie erben lediglich das Basisobjekt Pagevon MyPage. Alles Control, was Sie zurückgeben möchten, MyPageanstatt Pagedas PageEigentum des neu zu definierenControl


1

Ja, es unterstützt die Kovarianz, aber es hängt davon ab, was genau Sie erreichen möchten.

Ich neige auch dazu, Generika häufig für Dinge zu verwenden, was bedeutet, dass, wenn Sie etwas tun wie:

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

}

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

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

Und nicht Ihre Typen "verlieren".


1

Dies ist eine Funktion für das kommende C # 9.0 (.Net 5), von dem Sie jetzt eine Vorschau-Version herunterladen können .

Der folgende Code baut nun erfolgreich (ohne Angabe: 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

Ich habe es nicht ausprobiert, aber funktioniert das nicht?

YourPageType myPage = (YourPageType)yourControl.Page;

1
Ja, tut es. Ich möchte nur versuchen, das Casting überall zu vermeiden.
Kyle Sletten

0

Ja. Es gibt mehrere Möglichkeiten, dies zu tun, und dies ist nur eine Option:

Sie können Ihre Seite dazu bringen, eine benutzerdefinierte Oberfläche zu implementieren, die eine Methode namens "GetContext" oder ähnliches verfügbar macht und Ihre spezifischen Informationen zurückgibt. Dann kann Ihre Steuerung einfach die Seite anfordern und Folgendes besetzen:

var myContextPage = this.Page as IMyContextGetter;

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

Dann können Sie diesen Kontext verwenden, wie Sie möchten.


0

Sie können von jedem Steuerelement aus auf Ihre Seite zugreifen, indem Sie den übergeordneten Baum nach oben gehen. Das ist

myParent = this;

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

* Nicht kompiliert oder getestet.

Oder rufen Sie die übergeordnete Seite im aktuellen Kontext ab (abhängig von Ihrer Version).


Dann mache ich Folgendes: Ich erstelle eine Schnittstelle mit den Funktionen, die ich im Steuerelement verwenden möchte (zum Beispiel IHostingPage).

Dann habe ich die übergeordnete Seite 'IHostingPage host = (IHostingPage) Parent;' und ich bin bereit, die Funktion auf der Seite, die ich benötige, von meiner Steuerung aus aufzurufen.


0

Ich werde es so machen:

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; } }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.