C # supporta la covarianza del tipo restituito?


88

Sto lavorando con il framework .NET e voglio davvero essere in grado di creare un tipo di pagina personalizzato che utilizzi tutto il mio sito web. Il problema si verifica quando cerco di accedere alla pagina da un controllo. Voglio essere in grado di restituire il mio tipo specifico di pagina invece della pagina predefinita. C'è un modo per fare questo?

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

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

Risposte:


175

AGGIORNAMENTO: Questa risposta è stata scritta nel 2011. Dopo due decenni di persone che hanno proposto la covarianza del tipo restituito per C #, sembra che sarà finalmente implementata; Sono piuttosto sorpreso. Vedere la parte inferiore di https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ per l'annuncio; Sono sicuro che seguiranno i dettagli.


Sembra che quello che vuoi è la covarianza del tipo restituito. C # non supporta la covarianza del tipo restituito.

La covarianza del tipo restituito è il punto in cui si sostituisce un metodo di classe base che restituisce un tipo meno specifico con uno che restituisce un tipo più specifico:

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

Questo è sicuro perché i consumatori di Contenuti tramite custodia si aspettano un animale e Aquarium promette non solo di soddisfare tale requisito, ma anche di fare una promessa più rigorosa: che l'animale è sempre un pesce.

Questo tipo di covarianza non è supportato in C # ed è improbabile che venga mai supportato. Non è supportato da CLR. (È supportato da C ++ e dall'implementazione C ++ / CLI su CLR; lo fa generando metodi di aiuto magico del tipo che suggerisco di seguito.)

(Alcuni linguaggi supportano anche la controvarianza del tipo di parametro formale: è possibile sovrascrivere un metodo che accetta un pesce con un metodo che accetta un animale. Anche in questo caso, il contratto è rispettato; la classe base richiede che qualsiasi pesce venga gestito e il derivato class promette di gestire non solo i pesci, ma qualsiasi animale. Allo stesso modo, C # e CLR non supportano la controvarianza del tipo di parametro formale.)

Il modo in cui puoi aggirare questa limitazione è fare qualcosa come:

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

Ora ottieni sia i vantaggi di sovrascrivere un metodo virtuale, sia di ottenere una digitazione più forte quando usi qualcosa di tipo Aquarium in fase di compilazione.


3
Perfetto! Mi sono dimenticato di nascondermi!
Kyle Sletten

3
buona risposta come sempre. Ci sono mancate quelle risposte per un po '.
Dhananjay

4
Esiste una motivazione per non supportare la covarianza di ritorno e la controvarianza parametrica disponibile per la lettura ovunque?
porges

26
@EricLippert Essendo una groupie C #, sono sempre curioso del "backstage", quindi devo solo chiedere: è stata mai considerata la covarianza del tipo di ritorno per C # e ritenuto che avesse un ROI troppo basso? Capisco i vincoli di tempo e bugdet, ma dalla tua affermazione "improbabile che possa mai essere supportato" sembra che il team di progettazione preferirebbe in realtà NON averlo nella lingua. Dall'altro lato della barriera, Java ha introdotto questa funzionalità del linguaggio in Java 5, quindi mi chiedo quale sia la differenza nei principi generali e generali che governano il processo di progettazione del linguaggio.
Cristian Diaconescu

7
@Cyral: corretto; è sulla lista nel secchio "interesse moderato" - che è dove è stato per molto tempo! Potrebbe succedere, ma sono piuttosto scettico.
Eric Lippert

8

Con le interfacce ho aggirato il problema implementando esplicitamente l'interfaccia:

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

2

Inserendolo nell'oggetto MyControl funzionerebbe:

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

Non puoi sovrascrivere la proprietà perché restituisce un tipo diverso ... ma puoi ridefinirla.

Non è necessaria la covarianza in questo esempio, poiché è relativamente semplice. Tutto quello che stai facendo è ereditare l'oggetto di base Pageda MyPage. Qualunque Controlcosa tu voglia restituire MyPageinvece di Pagedeve ridefinire la Pageproprietà delControl


1

Sì, supporta la covarianza, ma dipende dall'esatto obiettivo che stai cercando di ottenere.

Inoltre tendo a usare molto i generici per le cose, il che significa che quando fai qualcosa come:

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

}

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

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

E non "perdere" i tuoi tipi.


1

Questa è una funzionalità per il prossimo C # 9.0 (.Net 5) di cui è possibile scaricare una versione di anteprima .

Il seguente codice ora costruisce con successo (senza dare: 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

Non l'ho provato, ma non funziona?

YourPageType myPage = (YourPageType)yourControl.Page;

1
Sì, lo fa. Voglio solo cercare di evitare casting dappertutto.
Kyle Sletten

0

Sì. Esistono diversi modi per farlo e questa è solo un'opzione:

Puoi fare in modo che la tua pagina implementi un'interfaccia personalizzata che esponga un metodo chiamato "GetContext" o qualcosa del genere e restituisca le tue informazioni specifiche. Quindi il tuo controllo può semplicemente richiedere la pagina e trasmettere:

var myContextPage = this.Page as IMyContextGetter;

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

Quindi puoi usare quel contesto come preferisci.


0

Puoi accedere alla tua pagina da qualsiasi controllo risalendo l'albero genitore. Questo è

myParent = this;

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

* Non è stato compilato o testato.

Oppure ottieni la pagina principale nel contesto corrente (dipende dalla tua versione).


Quindi quello che mi piace fare è questo: creo un'interfaccia con le funzioni che voglio usare nel controllo (ad esempio IHostingPage)

Quindi eseguo il cast della pagina padre "IHostingPage host = (IHostingPage) Parent;" e sono pronto per chiamare la funzione sulla pagina di cui ho bisogno dal mio controllo.


0

Lo farò in questo modo:

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; } }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.