-1

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

Martin Hansen Lennox
  • 2,837
  • 2
  • 23
  • 64
  • Why are you implementing interfaces on classes which inherit from classes which implement them? – SBFrancies Jul 20 '18 at 20:46
  • I wouldn't normally, but adding this explicitly to the class allowed me to see whether the error message changed as i tinkered with the code. – Martin Hansen Lennox Jul 20 '18 at 20:56
  • Why the downvote please? I would be nice to get an explanation as the question doesn't fall foul of any guidelines that I'm aware of. – Martin Hansen Lennox Jul 20 '18 at 21:15
  • Could you post your IPagedList interface (I don't know about the downvote)? – SBFrancies Jul 20 '18 at 21:24
  • What are you really trying to accomplish here? `Pages` returns a set of `AboutPage` not an `IPage`. This seems like an [XY Problem](http://xyproblem.info/) – JSteward Jul 20 '18 at 21:24
  • 1
    Possible duplicate of [Generic parameter - using a concrete type compiles, but an implemented interface does not](https://stackoverflow.com/questions/51450416/generic-parameter-using-a-concrete-type-compiles-but-an-implemented-interface) – George Helyar Jul 20 '18 at 21:24
  • @SBFrancies - it's from the original version of Troy Goode's PagedList here: https://github.com/troygoode/PagedList/blob/master/src/PagedList/IPagedList.cs (I will add it to the question) – Martin Hansen Lennox Jul 20 '18 at 21:31
  • @JSteward - I want to have other types of `IPage` too - e.g. `ProfilePage`, `InstructionsPage`, etc – Martin Hansen Lennox Jul 20 '18 at 21:34
  • @GeorgeHelyar - yes it is. I'm not intending to double post here, I just realised it might be better to break it down into smaller components (which I mentioned int the update above). Would it be good form to delete this question? – Martin Hansen Lennox Jul 20 '18 at 21:38

1 Answers1

0
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.

JSteward
  • 6,833
  • 2
  • 21
  • 30