web-dev-qa-db-fra.com

C # prend-il en charge la covariance du type de retour?

Je travaille avec le framework .NET et je souhaite vraiment pouvoir créer un type de page personnalisé utilisé par tous les sites Web. Le problème survient lorsque j'essaie d'accéder à la page à partir d'un contrôle. Je veux pouvoir renvoyer mon type spécifique de page au lieu de la page par défaut. Y a-t-il un moyen de faire ça?

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

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

Cela ressemble à ce que vous voulez, c'est une covariance de type retour. C # ne prend pas en charge la covariance du type de retour.

La covariance des types de retour est l'endroit où vous substituez une méthode de classe de base qui renvoie un type moins spécifique avec une autre renvoyant un type plus spécifique:

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

Ceci est sûr car les consommateurs de Contenu via Enceinte attendent un animal et Aquarium promet non seulement de remplir cette condition, mais aussi de faire une promesse plus stricte: que l'animal est toujours un poisson.

Ce type de covariance n'est pas pris en charge en C # et ne sera probablement jamais pris en charge. Il n'est pas pris en charge par le CLR. (Il est pris en charge par C++ et par l'implémentation C++/CLI sur le CLR; il le fait en générant des méthodes d'assistance magique du type que je suggère ci-dessous.)

(Certaines langues supportent également le type de paramètre formel contravariance - vous pouvez remplacer une méthode qui prend un poisson avec une méthode qui prend un animal. Encore une fois, le contrat est rempli; la classe de base exige que tout poisson soit manipulé, et le La classe promet non seulement de manipuler les poissons, mais également tous les animaux. De même, C # et le CLR ne prennent pas en charge la contravariance de type de paramètre formel.)

Pour contourner cette limitation, procédez comme suit:

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

Maintenant, vous obtenez à la fois les avantages de remplacer une méthode virtuelle et d'obtenir une frappe plus puissante lorsque vous utilisez quelque chose de type compilation, Aquarium.

142
Eric Lippert

Le placer dans l'objet MyControl fonctionnerait comme suit:

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

Vous ne pouvez pas remplacer la propriété car elle renvoie un type différent ... mais vous pouvez la redéfinir.

Vous n'avez pas besoin de covariance dans cet exemple, car c'est relativement simple. Tout ce que vous faites, c'est hériter de l'objet de base Page de MyPage. Toute Control que vous voulez renvoyer MyPage au lieu de Page doit redéfinir la propriété Page de la Control

2
Zhais

Cette série d'articles de blog traite en détail de la question de la covariance des types de retour, et explique même comment procéder avec IL.

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/14/93495.aspx

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/16/93516.aspx

http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/19/93562.aspx

Désolé de simplement poster des liens, mais il est assez détaillé et citer des extraits ici ne serait pas utile. Il montre comment cela peut être réalisé en utilisant le code IL.

2
hatchet

Oui, cela prend en charge la covariance, mais cela dépend de l’objet exact que vous essayez d’atteindre.

J'ai aussi tendance à utiliser beaucoup de génériques pour certaines choses, ce qui signifie que lorsque vous faites quelque chose comme:

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

}

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

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

Et ne pas "perdre" vos types.

1
Cade Roux

Avec les interfaces, je l'ai contourné en implémentant explicitement l'interface:

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

Vous pouvez accéder à votre page à partir de n’importe quel contrôle en remontant l’arborescence parent. C'est

myParent = this;

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

* N'a pas compilé ou testé.

Ou obtenez la page parente dans le contexte actuel (dépend de votre version).


Ensuite, ce que j'aime faire est la suivante: je crée une interface avec les fonctions que je souhaite utiliser dans le contrôle (par exemple, IHostingPage)

Ensuite, je lance la page parent 'IHostingPage Host = (IHostingPage) Parent;' et je suis prêt à appeler la fonction de la page dont j'ai besoin depuis mon contrôle.

0
Hogan

Je n'ai pas essayé, mais est-ce que ça ne marche pas?

YourPageType myPage = (YourPageType)yourControl.Page;
0
AGoodDisplayName

Oui. Il y a plusieurs façons de le faire, et ce n'est qu'une option:

Vous pouvez faire en sorte que votre page implémente une interface personnalisée qui expose une méthode appelée "GetContext" ou quelque chose du genre, et renvoie vos informations spécifiques. Ensuite, votre contrôle peut simplement demander la page et transtyper:

var myContextPage = this.Page as IMyContextGetter;

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

Ensuite, vous pouvez utiliser ce contexte comme vous le souhaitez. 

0
Tejs

Je vais le faire de cette façon: 

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; } }
}
0
Indomitable