Search code examples
c#genericsinterfacecovariance

Is it possible for these classes to implement these interfaces?


I am trying to create the following classes.

But I am having struggling to get a couple of View Model classes to implement interfaces, as detailed below.

Data Model Classes:

public interface IPage
{
    string PageTitle { get; set; }
    string PageContent { get; set; }
}
public interface IDatedPage
    : IPage
{
    DateTime PageDate { get; set; }
}



public abstract class Page
    : IPage
{
    public string PageTitle { get; set; }
    public string PageContent { get; set; }
}
public abstract class DatedPage
    : Page
    , IDatedPage
{
    public DateTime PageDate { get; set; }
}



public class AboutPage
    : Page
    , IPage
{
}
public class NewsPage 
    : DatedPage
    , IDatedPage
{
}

Related View Model Classes:

public interface IPageAdminViewModel<T>
    where T : IPage
{
    IPagedList<T> Pages { get; set; }
}
public interface IDatedPageAdminViewModel<T>
    : IPageAdminViewModel<T>
    where T : IDatedPage
{
}


public abstract class PageAdminViewModel<T>
    : IPageAdminViewModel<T>
    where T: IPage
{
    public IPagedList<T> Pages { get; set; }
}
public abstract class DatedPageAdminViewModel<T>
    : PageAdminViewModel<T>
    , IDatedPageAdminViewModel<T>
    where T : IDatedPage
{
}


public class AboutPageAdminViewModel
    : PageAdminViewModel<AboutPage>
    , IPageAdminViewModel<IPage>
{
}

public class NewsPageAdminViewModel
    : DatedPageAdminViewModel<NewsPage>
    , IDatedPageAdminViewModel<IDatedPage>
{
}

The Problem

I can't get AboutPageAdminViewModel to implement IPageAdminViewModel<IPage>.

Similarly NewsPageAdminViewModel won't implement IDatedPageAdminViewModel<IDatedPage>.

'AboutPageAdminViewModel' does not implement interface member 'IPageAdminViewModel<IPage>.Pages'. 'PageAdminViewModel<AboutPage>.Pages' cannot implement 'IPageAdminViewModel<IPage>.Pages' because it does not have the matching return type of 'IPagedList<IPage>'.


What I've Tried 1:

I have tried adding out to the generic parameter on the interface definition, like this:

public interface IPageAdminViewModel<out T>
    where T : IPage
{
    IPagedList<T> Pages { get; set; }
}
public interface IDatedPageAdminViewModel<out T>
    : IPageAdminViewModel<T>
    where T : IDatedPage
{
}

But it gives me the error:

Invalid variance: The type parameter 'T' must be invariantly valid on 'IPageAdminViewModel.Pages'. 'T' is covariant.

It won't let me add out to the .Pages property:

Invalid variance modifier. Only interface and delegate type parameters can be specified as variant.


What I've Tried 2:

I've learned that the out parameter doesn't play nicely with List<>, so I tried changing all IPagedList properties to IEnumerable. But I am still getting;

'AboutPageAdminViewModel' does not implement interface member 'IPageAdminViewModel<IPage>.Pages'. 'PageAdminViewModel<AboutPage>.Pages' cannot implement 'IPageAdminViewModel<IPage>.Pages' because it does not have the matching return type of 'IEnumerable<IPage>'.


Update

I'm trying to break it down into smaller questions of which this is the first:

Generic parameter - using a concrete type compiles, but an implemented interface does not


Solution

  • public class AboutPageAdminViewModel
        : PageAdminViewModel<AboutPage>
        , IPageAdminViewModel<IPage>
    {
    }
    

    The two constraints you have on the class contradict one another. PageAdminViewModel<AboutPage> says that the Pages collection returns a set of AboutPages and will accept only an AboutPage. While, IPageAdminViewModel<IPage> says that Pages returns any IPage and can take any IPage too. So while one constraint guarantees only AboutPage the other constraint opens the door to any IPage. In short not allowed, if it were allowed, you could get a ProfilePage out of the Pages collection simply because it too was an IPage. The only way you can get both behaviors is by implementing each Pages collection separately.