Quel est le but de cette auto-référence apparente en C #?


21

J'évalue un CMS open source appelé Piranha ( http://piranhacms.org/ ) pour l'utiliser dans l'un de mes projets. J'ai trouvé le code suivant intéressant et un peu déroutant, du moins pour moi. Certains peuvent-ils m'aider à comprendre pourquoi la classe hérite d'une base de même type?

public abstract class BasePage<T> : Page<T> where T : BasePage<T>
{
    /// <summary>
    /// Gets/sets the page heading.
    /// </summary>
    [Region(SortOrder = 0)]
    public Regions.PageHeading Heading { get; set; }
}

Si une classe de BasePage<T>est définie, pourquoi hériter de Page<T> where T: BasePage<T>? À quoi sert-il précisément?



7
Malgré les votes négatifs et les votes serrés, je pense que la communauté s'est trompée sur celui-ci. Il s'agit d'une question clairement formulée, liée à une décision de conception spécifique et non triviale, l'essence même de ce que ce site propose.
Robert Harvey

Il suffit de traîner et de le rouvrir lorsqu'il est fermé.
David Arno

Lisez sur le concept du polymorphisme lié à F :)
Eyvind

1
@Eyvind je l'ai fait. Pour ceux intéressés à lire sur le polymorphisme F-Bounded, voici le lien staff.ustc.edu.cn/~xyfeng/teaching/FOPL/lectureNotes/…
Xami Yen

Réponses:


13

Certains peuvent-ils m'aider à comprendre pourquoi la classe hérite d'une base de même type?

Ce n'est pas le cas, il hérite de Page<T>, mais Tlui-même est contraint d'être paramétré par un type dérivé de BasePage<T>.

Pour déduire pourquoi, vous devez regarder comment le paramètre type Test réellement utilisé. Après quelques recherches, en remontant la chaîne d'héritage, vous arriverez à cette classe:

( github )

public class GenericPage<T> : PageBase where T : GenericPage<T>
{
    public bool IsStartPage {
        get { return !ParentId.HasValue && SortOrder == 0; }
    }

    public GenericPage() : base() { }

    public static T Create(IApi api, string typeId = null)
    {
        return api.Pages.Create<T>(typeId);
    }
}

Pour autant que je puisse voir, le seul but de la contrainte générique est de s'assurer que la Createméthode renvoie le type le moins abstrait possible.

Je ne sais pas si cela en vaut la peine, cependant, mais il y a peut-être une bonne raison derrière cela, ou cela pourrait être uniquement pour des raisons de commodité, ou peut-être qu'il n'y a pas trop de substance derrière et c'est juste un moyen trop élaboré pour éviter un casting (BTW, I Je n'implique pas que ce soit le cas ici, je dis simplement que les gens le font parfois).

Notez que cela ne leur permet pas d'éviter la réflexion - le api.Pagesest un référentiel de pages qui obtient typeof(T).Nameet le transmet en tant que typeIdà la contentService.Createméthode ( voir ici ).


5

Une utilisation courante de ceci est liée au concept d'auto-types: un paramètre de type qui se résout au type actuel. Disons que vous voulez définir une interface avec une clone()méthode. La clone()méthode doit toujours renvoyer une instance de la classe sur laquelle elle a été appelée. Comment déclarez-vous cette méthode? Dans un système générique qui a des auto-types, c'est facile. Vous dites juste qu'il revient self. Donc, si j'ai une classe Foo, la méthode clone doit être déclarée pour retourner Foo. En Java et (à partir d'une recherche superficielle) C #, ce n'est pas une option. Vous voyez à la place des déclarations comme ce que vous voyez dans cette classe. Il est important de comprendre que ce n'est pas la même chose qu'un auto-type et que les restrictions qu'il offre sont plus faibles. Si vous avez un Fooet une Barclasse qui dérivent tous les deux deBasePage, vous pouvez (si je ne me trompe pas) définir Foo à paramétrer par Bar. Cela pourrait être utile, mais je pense que généralement, la plupart du temps, cela sera utilisé comme un auto-type et il est juste entendu que même si vous pouvez faire des farces et remplacer par d'autres types, ce n'est pas quelque chose que vous devriez faire. J'ai joué avec cette idée il y a longtemps, mais je suis arrivé à la conclusion que cela ne valait pas la peine car les limites des génériques Java. Les génériques C # sont bien sûr plus complets mais il semble avoir cette même limitation.

Une autre fois que cette approche est utilisée, c'est lorsque vous créez des types de graphes tels que des arbres ou d'autres structures récursives. La déclaration permet aux types de répondre aux exigences de Page, mais d'affiner davantage le type. Vous pouvez voir cela dans une arborescence. Par exemple, un Nodepourrait être paramétré par Nodepour permettre aux implémentations de définir qu'il ne s'agit pas seulement d'arbres contenant n'importe quel type de nœud, mais un sous-type spécifique de nœud (généralement leur propre type). Je pense que c'est plus ce qui se passe ici.


3

Étant la personne qui a réellement écrit le code, je peux confirmer que Filip est correct et que le générique auto-référencé est en fait une commodité pour fournir une méthode typée Create sur la classe de base.

Comme il le mentionne, il y a encore beaucoup de réflexion en cours, et à la fin, seul le nom du type est utilisé pour résoudre le type de page. La raison en est que vous pouvez également charger des modèles dynamiques, c'est-à-dire matérialiser le modèle sans avoir accès au type CLR qui l'a créé en premier lieu.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.