Learn how to efficiently implement sorted pagination in C# with a reusable SortedListPaginator
class. Discover lazy initialization, encapsulated sorting, and flexible pagination design using interfaces like IPaginatedCollection<T>
and IPage<T>
. Perfect for handling large datasets with optimized performance!
Designing a system to handle paginated, sorted data efficiently requires balancing flexibility, performance, and usability. In this article, we will create a class named SortedListPaginator
that implements the IPaginatedCollection<T>
interface. The core idea is to encapsulate the logic of sorting and pagination, ensuring that consumers do not need to worry about these details.
Overview of the Design
-
Input and Sorting:
- The class takes an unordered sequence as input.
- It ensures the sequence is sorted based on a custom criterion provided by the caller.
-
Lazy Initialization:
- Sorting is deferred until explicitly required, leveraging the
Lazy<T>
class to optimize performance.
- Sorting is deferred until explicitly required, leveraging the
-
Read-Only Behavior:
- Once sorted, the data becomes read-only, ensuring it can be safely shared across collaborating objects.
-
Pagination:
- The sorted data is divided into pages, each represented by a concrete implementation of the
IPage<T>
interface.
- The sorted data is divided into pages, each represented by a concrete implementation of the
The Key Components
IPaginatedCollection Interface
Defines the contract for the collection, allowing access to sorted pages and their metadata.
public interface IPaginatedCollection<T> : IEnumerable<IPage<T>>
{
int PageCount { get; }
IPage<T> this[int index] { get; }
}
IPage Interface
Represents a single page, exposing its content and metadata.
public interface IPage<T> : IEnumerable<T>
{
int Ordinal { get; }
int Count { get; }
int PageSize { get; }
}
SortedListPaginator Class
The SortedListPaginator
class encapsulates the logic for sorting and pagination.
Implementing the SortedListPaginator
Class Definition
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class SortedListPaginator<T> : IPaginatedCollection<T>
{
private readonly Lazy<List<T>> _sortedData;
private readonly int _pageSize;
public SortedListPaginator(IEnumerable<T> source, int pageSize, Func<T, object> sortKeySelector)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize), "Page size must be greater than zero.");
if (sortKeySelector == null) throw new ArgumentNullException(nameof(sortKeySelector));
_pageSize = pageSize;
// Lazy initialization for sorting
_sortedData = new Lazy<List<T>>(() => source.OrderBy(sortKeySelector).ToList());
}
// Total number of pages
public int PageCount => (int)Math.Ceiling((double)_sortedData.Value.Count / _pageSize);
// Access a specific page
public IPage<T> this[int index]
{
get
{
if (index < 0 || index >= PageCount)
throw new ArgumentOutOfRangeException(nameof(index), "Page index is out of range.");
var start = index * _pageSize;
var pageData = _sortedData.Value.Skip(start).Take(_pageSize);
return new Page<T>(pageData, index + 1, _pageSize);
}
}
// Enumerator for pages
public IEnumerator<IPage<T>> GetEnumerator()
{
for (int i = 0; i < PageCount; i++)
{
yield return this[i];
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
The Page Class
Each page is represented by a Page<T>
object that implements the IPage<T>
interface.
public class Page<T> : IPage<T>
{
private readonly List<T> _content;
public Page(IEnumerable<T> content, int ordinal, int pageSize)
{
_content = content.ToList();
Ordinal = ordinal;
PageSize = pageSize;
}
public int Ordinal { get; }
public int Count => _content.Count;
public int PageSize { get; }
public IEnumerator<T> GetEnumerator() => _content.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
How It Works
-
Lazy Initialization with
Lazy<T>
:- The
Lazy<List<T>>
ensures that sorting is deferred until the sorted data is accessed for the first time. - This approach avoids unnecessary computation if no pages are requested.
- The
-
Sorting in the Constructor:
- The sorting logic is encapsulated in a lambda function passed to the
Lazy<T>
instance. - The lambda executes the sorting (
OrderBy
) only once, ensuring efficiency.
- The sorting logic is encapsulated in a lambda function passed to the
-
Pagination Logic:
- The
this[int index]
property calculates the appropriate segment of the sorted data and returns it as aPage<T>
object. - The
Page<T>
object provides metadata and an enumerator for its content.
- The
Usage Example
class Program
{
static void Main()
{
// Sample data: unsorted numbers
var data = new List<int> { 5, 3, 1, 4, 2, 10, 9, 8, 7, 6 };
// Create a SortedListPaginator
var paginator = new SortedListPaginator<int>(
data,
pageSize: 3,
sortKeySelector: x => x // Sort by value
);
Console.WriteLine($"Total Pages: {paginator.PageCount}");
foreach (var page in paginator)
{
Console.WriteLine($"Page {page.Ordinal}:");
foreach (var item in page)
{
Console.WriteLine(item);
}
}
}
}
Output
Total Pages: 4
Page 1:
1
2
3
Page 2:
4
5
6
Page 3:
7
8
9
Page 4:
10
Advantages of the Design
-
Efficiency:
- Sorting occurs only once, and only when needed.
- Lazy initialization minimizes overhead.
-
Encapsulation:
- Sorting and pagination are fully encapsulated, freeing the consumer from dealing with these complexities.
-
Read-Only Behavior:
- The sorted list is immutable, ensuring safe usage across different parts of the application.
-
Flexibility:
- The design allows any sorting criterion through the
Func<T, object>
parameter.
- The design allows any sorting criterion through the
Key Takeaways
- Lazy Initialization is a powerful pattern for deferring costly operations until needed.
- Separating the sorting logic from pagination improves maintainability and scalability.
- Designing with interfaces ensures flexibility and encourages reusable, testable code.
This approach provides an efficient and clean solution for handling sorted, paginated data in any application.
Top comments (0)