J'évalue un CMS open source appelé Piranha ( http://piranhacms.org/ ) à 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 en cours de définition, pourquoi hériter de Page<T> where T: BasePage<T>
? À quoi sert-il précisément?
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 T
lui-même est contraint d'être paramétré par un type dérivé de BasePage<T>
.
Pour en déduire pourquoi, vous devez voir comment le paramètre de type T
est 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 méthode Create
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.Pages
Est un référentiel de pages qui obtient typeof(T).Name
, et le passe en tant que typeId
au contentService.Create
( voir ici ).
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. Supposons que vous souhaitiez définir une interface avec une méthode clone()
. La méthode clone()
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 simplement qu'il renvoie 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 rapide) C #, ce n'est pas une option. Vous voyez plutôt 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 en tant que self-type et les restrictions qu'il fournit sont plus faibles. Si vous avez une classe Foo
et Bar
qui dérivent toutes les deux de BasePage
, vous pouvez (si je ne suis pas erreur) définit 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 bêtises 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 limitations des génériques Java génériques. C # les génériques 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 Node
peut être paramétré par Node
pour permettre aux implémentations de définir qu'il ne s'agit pas uniquement d'arbres contenant tout type de Node mais un sous spécifique -type de Node (généralement leur propre type.) Je pense que c'est plus de ce qui se passe ici.
É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.