Applying Pagination to a Sorted List c#
mohamed Tayel
Posted on November 24, 2024
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.
Posted on November 24, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024