Pattern for exposing non-generic version of generic interface
Asked Answered
S

3

26

Say I have the following interface for exposing a paged list

public interface IPagedList<T>
{
    IEnumerable<T> PageResults { get; }
    int CurrentPageIndex { get; }
    int TotalRecordCount { get; }
    int TotalPageCount { get; }        
    int PageSize { get; }
}   

Now I want to create a paging control

public class PagedListPager<T>
{
    public PagedListPager<T>(IPagedList<T> list)
    {
        _list = list;
    }

    public void RenderPager()
    {
        for (int i = 1; i < list.TotalPageCount; i++)
            RenderLink(i);
    }
}

The paging control has no interest in T (the actual contents of the list). It only requires the number of pages, current page etc. So the only reason PagedListPager is generic is so that it will compile with the generic IPagedList<T> paramater.

Is this a code smell? Should I care that I effectively have a redundant generic?

Is there a standard pattern in a case like this for exposing an additional non-generic version of the interface, so I can remove the generic type on the pager?

public class PagedListPager(IPagedList list)

Edit

I thought I'd also add the current way I've solved this problem and invite comments on whether it's a suitable solution:

public interface IPagedList // non-generic version
{
    IEnumerable<object> PageResults { get; }
    int CurrentPageIndex { get; }
    int TotalRecordCount { get; }
    int TotalPageCount { get; }        
    int PageSize { get; }
}


public class ConcretePagedList<T> : IPagedList<T>, IPagedList
{
    #region IPagedList<T> Members

    public IEnumerable<T> PageResults { get; set; }
    public int CurrentPageIndex { get; set; }
    public int TotalRecordCount { get; set; }
    public int PageSize { get; set; }

    #endregion

    #region IPagedList Members

    IEnumerable<object> IPagedList.PageResults
    {
        get { return PageResults.Cast<object>(); }
    }

    #endregion
}

Now I can pass ConcretePagedList<T> to non-generic classes/functions

Scornik answered 8/7, 2011 at 10:28 Comment(3)
Is PagedListPager<T> a class or method declaration?Angola
@Gregg oops sorry I'll edit that.Scornik
I do not like having the properties defined in both interfaces as you could explicitly implement them to do different things. Eg, IPagedList.PageSize {get{return 8;}} IPageList<T>.PageSize{get{return this.PageResults.Count();}} The only reason you have the second interface is to provide strong typing, so Marc's answer seems to remove the ability for the class to have different results on the properties which should not be different.Coauthor
L
31

My approach here would be to use new to re-declare the PageResults, and expose the T as a Type:

public interface IPagedList
{
    int CurrentPageIndex { get; }
    int TotalRecordCount { get; }
    int TotalPageCount { get; }        
    int PageSize { get; }

    Type ElementType { get; }
    IEnumerable PageResults { get; }
}   

public interface IPagedList<T> : IPagedList
{
    new IEnumerable<T> PageResults { get; }
}  

This will, however, require "explicit interface implementation", i.e.

class Foo : IPagedList<Bar>
{
    /* skipped : IPagedList<Bar> implementation */

    IEnumerable IPagedList.PageResults {
        get { return this.PageResults; } // re-use generic version
    }
    Type IPagedList.ElementType {
        get { return typeof(Bar); }
    }
}

This approach makes the API fully usable via both the generic and non-generic API.

Lipson answered 8/7, 2011 at 10:40 Comment(3)
Thanks Marc, I like this it's quite similar to what I came up with but with a somewhat neater implementation.Scornik
@Moop that depends on how the signature is changing. Do you have a specific example?Lipson
@MarcGravell Something like this: public interface IBag { IFruit GetNextObject();} public interface IBag<T> where T : IFruit { new T GetNextObject();}Goliard
A
7

One option is to create 2 interfaces such that:

    public interface IPagedListDetails
    {
        int CurrentPageIndex { get; }
        int TotalRecordCount { get; }
        int TotalPageCount { get; }
        int PageSize { get; }
    }

    public interface IPagedList<T> : IPagedListDetails
    {
        IEnumerable<T> PageResults { get; }
    }

And then your control:

public class PagedListPager(IPagedListDetails details)
Adenoid answered 8/7, 2011 at 10:32 Comment(3)
I had considered this but was of the impression that interface inheritance was generally not considered a good idea?Scornik
There is no prob in Interface inheritance, if you look around .NET BCL you will find that there are various generic interfaces which inherits from non generic classesAdenoid
@fearofawhackplanet: IMHO interface inheritance is vastly underused. IMHO, something like IList should have derived from IReadableByIndex, IMutableByIndex, IAppendable, and IRemovableByIndex; arrays should have implemented the first two but not the last two.Delacourt
M
5

Define two interfaces, first

    public interface IPageSpecification
    {
        int CurrentPageIndex { get; }
        int TotalRecordCount { get; }
        int TotalPageCount { get; }        
        int PageSize { get; }
    }   

public interface IPagedList<T> : IPageSpecification
{
    IEnumerable<T> PageResults { get; }
}   

As you see, IPagedList is derived from IPageSpecification. In your method, only use IPageSpecification as parameter. In other cases, IPagedList - implementers of IPagedList would also contain data from IPageSpecification

Manama answered 8/7, 2011 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.