你想要的很常见:你想“每页”读取你的输入。
换句话说:你有一系列相似的项目,你想把它分成大小相等的子组。
如果您将经常使用它,请考虑为其创建一些类。通过这种方式,您可以将它用于需要“每页”获取项目的几个问题。
我经常使用它从每页的数据库中获取项目。由于是泛型类,我可以将 IQueryable / IEnumerable 放在 PageCollection 类中并询问页数和 Page[3]。
如果你做得很聪明,你就不必提取你不使用的项目,也不会重新提取你已经提取的项目。
我们隐藏页面的内部结构。因此我们创建了一个接口:
interface IPage<T> : IReadOnlyCollection<T>, IEnumerable<T>
{
int PageNr {get; } // the zero based number of the page
int PageCount {get; } // the total number of available pages
int PageLength {get; } // The number of items on this page
我选择实现IReadOnlyCollection<T>而不是IReadOnlyList<T>,因为索引通常给人的印象不正确。
例如,如果您有一个ProductPages 的集合,那么每个ProductPage 都有零个或多个Products。但是,如果您在ProductPage[10] 上并要求Product[3],您会期待什么?有些人可能会将其与带有主键 3 的 Product 混淆。
也许以下方法也很方便:
IPage<T> PreviousPage {get;}
IPage<T> NextPage {get;}
IPage<T> GetPage(int pageNr);
IReadOnlyList<T> PageCollection {get;}
}
首先让我们创建 PageCollection。 PageCollection 将创建页面:
class PageCollection<T> : IReadOnlyList<T>
{
private readonly IDictionary<int, IPage<T>> fetchedPages
= new Dictionary<int, IPage<T>();
private int pageCount = -1; // not known yet
public PageCollection<IEnumerable<T> query, pageSize)
{
// TODO: check input parameters for incorrect values
this.Query = query;
this.PageSize = pageSize;
}
public IEnumerable<T> Query {get;}
// TODO: consider IQueryable if you use databases / entity framework
public int PageSize {get;}
...
}
我们需要方法来获取页面的数量,并通过索引来获取页面:
public int Count
{
get
{
if (this.pageCount < 0)
this.pageCount = this.Query.Count();
return this.pageCount;
}
}
public IPage this[int pageIndex] => this.GetPage(pageIndex);
最后我们来到创建页面的部分:
public IPage<T> GetPage(int pageIndex)
{
if (0 < pageIndex || pageIndex >= this.Count)
{
// pageIndex out of range.
// TODO: decide whether to return null or throw exception
}
if (!this.fetchedPages.TryGetValue(pageIndex, out Page<T> fetchedPage)
{
// page never fetched before, fetch it now
fetchedPage = this.FetchPage(pageIndex);
this.FetchedPages.Add(pageIndex, fetchedPage);
}
return fetchedPage;
}
我决定将获取的页面保存在字典中,而不是列表中。这样,您可以在获取页面 0 到 4 之前请求 Page[5]。
private Page<T> FetchPage(int pageIndex)
{
return new Page(this, pageIndex);
}
嗯,这并没有多大作用:显然它是完成所有工作的页面。
是时候创建页面了。
您必须自己决定是立即阅读完整页面,还是仅在您要求时阅读
class Page<T> : IPage<T>, IReadOnlyCollection<T>, IEnumerable<T>
{
public Page(PageCollection<T> pageCollection, int pageNr)
{
this.PageCollection = pageCollection;
this.PageNr = pageNr;
// if you want to fetch the data immediately:
this.PageContent = this.Query.ToList();
}
public PageCollection<T> PageCollection {get;}
public int PageNr {get;}
public int PageCount => this.PageCollection.Count;
public IReadOnlyCollection<T> PageContent {get;}
public IEnumerable<T> Query => this.PageCollection.Query
.Skip(this.PageNr * this.PageSize)
.Take(this.PageSize);
}
IReadOnlyCollection<T> 和IEnumerable<T> 的实现相当简单,方法都调用this.PageContent:
IEnumerator<T> GetEnumerator() {return this.PageContent.GetEnumerator();}
int Count => this.PageContent.Count;
等等
PreviousPage / NextPage / GetPage 之类的“很高兴拥有”过程是单行的,因为它们可以通过询问 PageCollection 来处理:
IPage<T> PreviousPage => this.PageCollection.GetPage(this.PageNr-1);
当然,如果页面超出范围,您必须决定该怎么做:异常还是返回 null?
最后的用法:
const int pageSize = 25;
IEnumerable<Product> queryProducts = ...
PageCollection<Product> productPages =
new PageCollection<Product>(queryProducts, pageSize);
Page<Product> productPage = productPages.FirstOrDefault();
// this page can be used as a collection of Products
DisplayProducts(productPage);
// get the next page:
Page<Product> nextPage = productPage.NextPage;