I am evaluating an open source CMS called Piranha (http://piranhacms.org/) for use in one of my projects. I found the following code interesting and a bit confusing, at least to me. Can some help me understand why the class is inheriting from a base of same 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; }
}

If a class of BasePage<T> is being defined, why inherit from Page<T> where T: BasePage<T>? What specific purpose does it serve?

up vote 9 down vote accepted

Can some help me understand why the class is inheriting from a base of same type?

It's not, it's inheriting from Page<T>, but T itself is constrained to be parameterized by a type that is derived from BasePage<T>.

To infer why, you have to look at how the type parameter T is actually used. After some digging, as you move up the inheritance chain, you'll come up upon this class:

(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);
    }
}

As far as I can see, the only purpose for the generic constraint is to make sure that the Create method returns the least abstract type possible.

Not sure if it's worth it, though, but perhaps there's some good reason behind it, or it could be only for convenience, or maybe there's no too much substance behind it and it's just an overly elaborate way to avoid a cast (BTW, I'm not implying that is the case here, I'm just saying that people do that sometimes).

Note that this doesn't allow them to avoid reflection - the api.Pages is a repository of pages that obtains typeof(T).Name, and passes it as the typeId to the contentService.Create method (see here).

One common use of this is related to the concept of self-types: a type parameter that resolves to the current type. Let's say you want to define an interface with a clone() method. The clone() method must always return an instance of the class on which it was called. How do you declare that method? In a generics system that has self-types, it's easy. You just say it returns self. So if I have a class Foo, the clone method must be declared to return Foo. In Java and (from a cursory search) C#, this isn't an option. You instead see declarations like what you see in this class. It's important to understand that this is not the same thing as a self-type and the restrictions it provides are weaker. If you have a Foo and a Bar class that both derive from BasePage, you can (if I am not mistaken) define Foo to be parameterized by Bar. That might be useful but I think typically, most of the time, this will be used like a self-type and it's just understood that even though you can goof around and substitute with other types, it's not something you should do. I played around with this idea long ago but came to the conclusion that it wasn't worth the effort because the limitations of Java generics. C# generics are of course more fully featured but it seems to have this same limitation.

Another time this approach get's used is when you are building graph-like types such as trees or other recursive structures. The declaration allows types to meet the requirements of Page but further refine the type. You might see this in a tree structure. For example a Node might be parameterized by Node to allow for implementations to define that they aren't just Trees containing any type of Node but a specific sub-type of Node (usually their own type.) I think that's more of what's going on here.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.