├── tests ├── Paging.Tests.UnitTests │ ├── GlobalUsings.cs │ ├── Lab.cs │ ├── Extensions │ │ └── PagedList │ │ │ ├── PaginatedWithoutPagerTests.cs │ │ │ └── PaginatedWithPagerTests.cs │ ├── PagedCollections │ │ └── PagedList │ │ │ ├── GetPagerDataTests.cs │ │ │ ├── ThisIndexTests.cs │ │ │ └── ConstructorsTests.cs │ ├── Paging.Tests.UnitTests.csproj │ └── Pagers │ │ └── PagerConstructorsTests.cs ├── Paging.Tests.Console │ ├── Program.cs │ ├── Paging.Tests.Console.csproj │ └── Tests │ │ └── JsonSerializationTests.cs └── Paging.Tests.WebApplication.Net8 │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Paging.WebApplicationTest.Net8.http │ ├── Paging.Tests.WebApplication.Net8.csproj │ ├── Program.cs │ └── Properties │ └── launchSettings.json ├── docs ├── toc.yml ├── docs │ ├── toc.yml │ ├── getting-started.md │ ├── using-paged-list.md │ └── contributing.md ├── docfx.json └── index.md ├── global.json ├── .gitignore ├── benchmarks └── Paging.Benchmarks │ ├── Program.cs │ ├── Paging.Benchmarks.csproj │ └── Loops │ ├── IntsBenchmark.cs │ └── StringBenchmark.cs ├── Paging.sln.DotSettings.user ├── src └── Paging │ ├── Abstractions │ ├── IPagedList.cs │ └── IPager.cs │ ├── Paging.csproj │ ├── README.md │ ├── Extensions │ └── PagedListConstructorExtensions.cs │ ├── Pagers │ └── Pager.cs │ └── PagedList.cs ├── LICENSE ├── .github └── workflows │ ├── paging-ci.yml │ ├── github-page-deploy.yml │ └── paging-cd.yml ├── Paging.sln └── README.md /tests/Paging.Tests.UnitTests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Docs 2 | href: docs/ 3 | - name: API 4 | href: api/ -------------------------------------------------------------------------------- /tests/Paging.Tests.Console/Program.cs: -------------------------------------------------------------------------------- 1 | using Paging.Tests.Console.Tests; 2 | 3 | JsonSerializationTests.Test(); -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vs 3 | .vscode 4 | _site/ 5 | bin/ 6 | obj/ 7 | docs/api/ 8 | /packages/ 9 | riderModule.iml 10 | /_ReSharper.Caches/ 11 | -------------------------------------------------------------------------------- /benchmarks/Paging.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using Paging.Benchmarks.Loops; 3 | 4 | BenchmarkRunner.Run(); -------------------------------------------------------------------------------- /docs/docs/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Getting Started 2 | href: getting-started.md 3 | - name: Using PagedList 4 | href: using-paged-list.md 5 | - name: Contributing 6 | href: contributing.md -------------------------------------------------------------------------------- /tests/Paging.Tests.WebApplication.Net8/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/Paging.Tests.WebApplication.Net8/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /tests/Paging.Tests.WebApplication.Net8/Paging.WebApplicationTest.Net8.http: -------------------------------------------------------------------------------- 1 | @Paging.WebApplicationTest_HostAddress = http://localhost:5084 2 | 3 | GET {{Paging.WebApplicationTest_HostAddress}}/paging?pageNumber=1&pageSize=10 4 | Accept: application/json 5 | 6 | 7 | ### 8 | -------------------------------------------------------------------------------- /docs/docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 |
3 | 4 | ## Installation 5 | 6 | Using the NuGet package manager console within Visual Studio run the following command: 7 | 8 | ``` 9 | Install-Package Qrtix.Paging 10 | ``` 11 | 12 | Or using the .NET Core CLI from a terminal window: 13 | 14 | ``` 15 | dotnet add package Qrtix.Paging 16 | ``` 17 | -------------------------------------------------------------------------------- /tests/Paging.Tests.WebApplication.Net8/Paging.Tests.WebApplication.Net8.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | default 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /benchmarks/Paging.Benchmarks/Paging.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | default 6 | true 7 | 8 | Exe 9 | enable 10 | enable 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/Lab.cs: -------------------------------------------------------------------------------- 1 | using Paging.Pagers; 2 | 3 | namespace Paging.Tests.UnitTests; 4 | 5 | public static class Lab 6 | { 7 | public static class DataSources 8 | { 9 | public static class Pagers 10 | { 11 | public static Pager PageNumberFirst => new(1, 10, 50); 12 | public static Pager PageNumberLast => new(5, 10, 50); 13 | } 14 | 15 | public static class IntegerLists 16 | { 17 | public static List Items50 => 18 | [ 19 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 20 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 21 | ]; 22 | public static List Empty => []; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/Extensions/PagedList/PaginatedWithoutPagerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Paging.Extensions; 3 | 4 | namespace Paging.Tests.UnitTests.Extensions.PagedList; 5 | 6 | public class PaginatedWithoutPagerTests 7 | { 8 | [Fact] 9 | public void Paginated() 10 | { 11 | // Act 12 | var pagedList = Lab.DataSources.IntegerLists.Items50.Paginate(2, 10); 13 | 14 | // Assert 15 | pagedList.PageNumber.Should().Be(2); 16 | pagedList.PageSize.Should().Be(10); 17 | pagedList.TotalItemCount.Should().Be(50); 18 | pagedList.Count.Should().Be(10); 19 | pagedList.IsLastPage.Should().BeFalse(); 20 | pagedList.IsFirstPage.Should().BeFalse(); 21 | pagedList.HasPreviousPage.Should().BeTrue(); 22 | pagedList.HasNextPage.Should().BeTrue(); 23 | } 24 | } -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/Extensions/PagedList/PaginatedWithPagerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Paging.Extensions; 3 | 4 | namespace Paging.Tests.UnitTests.Extensions.PagedList; 5 | 6 | public class PaginatedWithPagerTests 7 | { 8 | [Fact] 9 | public void Paginated() 10 | { 11 | // Act 12 | var pagedList = Lab.DataSources.IntegerLists.Items50.Paginate(Lab.DataSources.Pagers.PageNumberFirst); 13 | 14 | // Assert 15 | pagedList.PageNumber.Should().Be(1); 16 | pagedList.PageSize.Should().Be(10); 17 | pagedList.TotalItemCount.Should().Be(50); 18 | pagedList.Count.Should().Be(10); 19 | pagedList.IsLastPage.Should().BeFalse(); 20 | pagedList.IsFirstPage.Should().BeTrue(); 21 | pagedList.HasPreviousPage.Should().BeFalse(); 22 | pagedList.HasNextPage.Should().BeTrue(); 23 | } 24 | } -------------------------------------------------------------------------------- /Paging.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | <SessionState ContinuousTestingMode="0" IsActive="True" Name="ConstructorsTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 4 | <Project Location="D:\personal\dotnet-libs\Paging\tests\Paging.Tests.UnitTests" Presentation="&lt;tests&gt;\&lt;Paging.Tests.UnitTests&gt;" /> 5 | </SessionState> 6 | -------------------------------------------------------------------------------- /tests/Paging.Tests.Console/Paging.Tests.Console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0;net7.0;net6.0;netstandard1.3;netstandard2.0;netstandard2.1 6 | enable 7 | enable 8 | default 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | DOES_NOT_SUPPORT_JSON 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/PagedCollections/PagedList/GetPagerDataTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | namespace Paging.Tests.UnitTests.PagedCollections.PagedList; 4 | 5 | public class GetPagerDataTests 6 | { 7 | [Fact] 8 | public void GetPagerData() 9 | { 10 | // Arrange 11 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 12 | var pagedList = new PagedList( 13 | Lab.DataSources.IntegerLists.Items50, 14 | pager 15 | ); 16 | 17 | // Act 18 | var newPager = pagedList.GetPagerData(); 19 | 20 | // Assert 21 | newPager.IsFirstPage.Should().Be(pager.IsFirstPage); 22 | newPager.IsLastPage.Should().Be(pager.IsLastPage); 23 | newPager.PageNumber.Should().Be(pager.PageNumber); 24 | newPager.PageSize.Should().Be(pager.PageSize); 25 | newPager.TotalItemCount.Should().Be(pager.TotalItemCount); 26 | newPager.PageCount.Should().Be(pager.PageCount); 27 | newPager.HasPreviousPage.Should().Be(pager.HasPreviousPage); 28 | newPager.HasNextPage.Should().Be(pager.HasNextPage); 29 | } 30 | } -------------------------------------------------------------------------------- /tests/Paging.Tests.WebApplication.Net8/Program.cs: -------------------------------------------------------------------------------- 1 | using Paging.Extensions; 2 | 3 | var builder = WebApplication.CreateBuilder(args); 4 | 5 | // Add services to the container. 6 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 7 | builder.Services.AddEndpointsApiExplorer(); 8 | builder.Services.AddSwaggerGen(); 9 | 10 | var app = builder.Build(); 11 | 12 | // Configure the HTTP request pipeline. 13 | if (app.Environment.IsDevelopment()) 14 | { 15 | app.UseSwagger(); 16 | app.UseSwaggerUI(); 17 | } 18 | 19 | app.UseHttpsRedirection(); 20 | 21 | app.MapGet( 22 | "/paging", 23 | (int pageNumber, int pageSize) => 24 | { 25 | var list = new List { 26 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 27 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 28 | }; 29 | 30 | return list.Paginate(pageNumber, pageSize); 31 | } 32 | ) 33 | .WithName("PagedList") 34 | .WithOpenApi(); 35 | 36 | app.Run(); -------------------------------------------------------------------------------- /src/Paging/Abstractions/IPagedList.cs: -------------------------------------------------------------------------------- 1 | namespace Paging.Abstractions; 2 | 3 | /// 4 | /// Represents a paged list of items along with pagination information. 5 | /// 6 | public interface IPagedList : IPager 7 | { 8 | /// 9 | /// Gets a value indicating whether the is empty based on the total item count. 10 | /// 11 | /// True if the has no items; otherwise, false. 12 | bool IsEmpty { get; } 13 | 14 | /// 15 | /// Retrieves a copied data from the current . 16 | /// 17 | /// A copied data from the current . 18 | IPager GetPagerData(); 19 | } 20 | 21 | /// 22 | /// Represents a paged list of items along with pagination information. 23 | /// 24 | /// The type of items in the paged list. 25 | public interface IPagedList : IPager { } -------------------------------------------------------------------------------- /docs/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "src": "../src", 7 | "files": [ 8 | "**/*.csproj" 9 | ] 10 | } 11 | ], 12 | "dest": "api", 13 | "allowCompilationErrors": true, 14 | "outputFormat": "markdown" 15 | } 16 | ], 17 | "build": { 18 | "content": [ 19 | { 20 | "files": [ 21 | "**/*.{md,yml}" 22 | ], 23 | "exclude": [ 24 | "_site/**" 25 | ] 26 | } 27 | ], 28 | "resource": [ 29 | { 30 | "files": [ 31 | "images/**" 32 | ] 33 | } 34 | ], 35 | "output": "_site", 36 | "template": [ 37 | "default", 38 | "modern" 39 | ], 40 | "globalMetadata": { 41 | "_appName": "Paging", 42 | "_appTitle": "Paging", 43 | "_enableSearch": true, 44 | "_appFooter": "Copyright © 2024 Carlos J. Ortiz", 45 | "_disableContribution": true, 46 | "_lang": "EN", 47 | "pdf": false 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Carlos J. Ortiz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/Paging.Tests.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/paging-ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: Paged List CI 5 | 6 | on: 7 | push: 8 | branches: [ "develop" ] 9 | pull_request: 10 | branches: [ "develop" ] 11 | 12 | env: 13 | PATH_PROJECT: src\Paging 14 | PATH_RELEASE: src\Paging\bin\Release 15 | PROJECT_NAME: Paging.csproj 16 | PATH_TEST: tests\Paging.Tests.UnitTests 17 | 18 | jobs: 19 | build: 20 | name: Build & Test Paging project 21 | runs-on: windows-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Setup .NET 25 | uses: actions/setup-dotnet@v3 26 | with: 27 | dotnet-version: | 28 | 8.x 29 | 7.x 30 | 6.x 31 | 2.1.x 32 | 2.0.x 33 | # cache: true 34 | - name: Restore dependencies 35 | run: dotnet restore 36 | working-directory: ${{ env.PATH_PROJECT }} 37 | - name: Build 38 | run: dotnet build --no-restore --configuration Debug 39 | working-directory: ${{ env.PATH_PROJECT }} 40 | - name: Run Paging Tests 41 | run: dotnet test --verbosity normal 42 | working-directory: ${{ env.PATH_TEST }} -------------------------------------------------------------------------------- /tests/Paging.Tests.WebApplication.Net8/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:22598", 8 | "sslPort": 44362 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5084", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "https": { 23 | "commandName": "Project", 24 | "dotnetRunMessages": true, 25 | "launchBrowser": true, 26 | "launchUrl": "swagger", 27 | "applicationUrl": "https://localhost:7236;http://localhost:5084", 28 | "environmentVariables": { 29 | "ASPNETCORE_ENVIRONMENT": "Development" 30 | } 31 | }, 32 | "IIS Express": { 33 | "commandName": "IISExpress", 34 | "launchBrowser": true, 35 | "launchUrl": "swagger", 36 | "environmentVariables": { 37 | "ASPNETCORE_ENVIRONMENT": "Development" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/docs/using-paged-list.md: -------------------------------------------------------------------------------- 1 | # Using PagedList 2 | 3 | The `PagedList` class in the Qrtix.Paging library provides a convenient way to paginate large collections of data efficiently. Here's how you can use it: 4 | 5 | ## Instantiate a PagedList: 6 | 7 | ```csharp 8 | // Assuming you have a list of items named 'sourceList' and a page size of 10 9 | var pagedList = new PagedList(sourceList, pageNumber, pageSize); 10 | ``` 11 | 12 | ## Access Paginated Data: 13 | 14 | You can access the data in the paged list using indexers or enumeration: 15 | 16 | ```csharp 17 | // Accessing items using indexer 18 | var item = pagedList[index]; 19 | 20 | // Enumerating through the paged list 21 | foreach (var item in pagedList) 22 | { 23 | // Process each item 24 | } 25 | 26 | // Using for loop 27 | for (int i = 0; i < pagedList.Count; i++) 28 | { 29 | var item = pagedList[i]; 30 | // Process each item 31 | } 32 | ``` 33 | 34 | ## Retrieve Pagination Information: 35 | 36 | You can also retrieve pagination-related information such as the total number of pages, total items, etc.: 37 | 38 | ```csharp 39 | // Total number of items 40 | var totalItems = pagedList.TotalItemCount; 41 | 42 | // Total number of pages 43 | var totalPages = pagedList.PageCount; 44 | 45 | // Current page number 46 | var currentPage = pagedList.PageNumber; 47 | 48 | // Page size 49 | var pageSize = pagedList.PageSize; 50 | ``` 51 | 52 | -------------------------------------------------------------------------------- /.github/workflows/github-page-deploy.yml: -------------------------------------------------------------------------------- 1 | # Your GitHub workflow file under .github/workflows/ 2 | 3 | name: Github Page deploy 4 | 5 | # Trigger the action on push to master 6 | on: 7 | push: 8 | branches: ["master", "github-page"] 9 | 10 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 11 | permissions: 12 | actions: read 13 | pages: write 14 | id-token: write 15 | 16 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 17 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 18 | concurrency: 19 | group: "pages" 20 | cancel-in-progress: false 21 | 22 | jobs: 23 | publish-docs: 24 | environment: 25 | name: github-pages 26 | url: ${{ steps.deployment.outputs.page_url }} 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | - name: Dotnet Setup 32 | uses: actions/setup-dotnet@v3 33 | with: 34 | dotnet-version: 8.x 35 | 36 | - run: dotnet tool update -g docfx 37 | - run: docfx docs/docfx.json 38 | 39 | - name: Upload artifact 40 | uses: actions/upload-pages-artifact@v3 41 | with: 42 | # Upload entire repository 43 | path: 'docs/_site' 44 | - name: Deploy to GitHub Pages 45 | id: deployment 46 | uses: actions/deploy-pages@v4 47 | -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/PagedCollections/PagedList/ThisIndexTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Paging.Extensions; 3 | 4 | namespace Paging.Tests.UnitTests.PagedCollections.PagedList; 5 | 6 | public class ThisIndexTests 7 | { 8 | [Theory] 9 | [MemberData(nameof(ValidIndexes))] 10 | public void DirectIndexing_WithValidIndexes_ReturnsTheItemIndexed(int index) 11 | { 12 | // Arrange 13 | var list = Lab.DataSources.IntegerLists.Items50; 14 | var pagedList = list.Paginate(Lab.DataSources.Pagers.PageNumberFirst); 15 | 16 | // Act 17 | var item = pagedList[index]; 18 | 19 | // Assert 20 | item.Should().Be(list[index]); 21 | } 22 | 23 | [Theory] 24 | [MemberData(nameof(InvalidIndexes))] 25 | public void DirectIndexing(int index) 26 | { 27 | // Arrange 28 | var pagedList = Lab.DataSources.IntegerLists.Items50.Paginate(Lab.DataSources.Pagers.PageNumberFirst); 29 | 30 | // Act 31 | var act = () => 32 | { 33 | var item = pagedList[index]; 34 | }; 35 | 36 | // Assert 37 | act.Should().Throw(); 38 | } 39 | 40 | public static IEnumerable ValidIndexes => new List 41 | { 42 | new object[] { 0 }, 43 | new object[] { 3 }, 44 | new object[] { Lab.DataSources.Pagers.PageNumberFirst.PageCount }, 45 | }; 46 | 47 | public static IEnumerable InvalidIndexes => new List 48 | { 49 | new object[] { -1 }, 50 | new object[] { Lab.DataSources.Pagers.PageNumberFirst.PageCount + 5 } 51 | }; 52 | } -------------------------------------------------------------------------------- /docs/docs/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | **Did you find a bug?** 4 | 5 | - Ensure the bug was not already reported by searching on GitHub 6 | under [Issues](https://github.com/carlosjortiz/Paging/issues). 7 | - If you're unable to find an open issue addressing the 8 | problem, [open a new one](https://github.com/carlosjortiz/Paging/issues/new). Be sure to include a title and clear 9 | description, as much relevant information as possible, and a code sample or an executable test case demonstrating the 10 | expected behavior that is not occurring. 11 | 12 | **Did you write a patch that fixes a bug?** 13 | 14 | - Open a new GitHub pull request with the patch. 15 | - Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 16 | 17 | **Do you intend to add a new feature or change an existing one?** 18 | 19 | - First suggest your change in the [Paging ideas page](https://github.com/carlosjortiz/Paging/discussions/categories/ideas) 20 | for discussion. 21 | - There are no fixed rules on what should and shouldn't be in this library, but some features are more valuable than 22 | others, and some require long-term maintenance that outweighs the value of the feature. So please get sign-off from 23 | the 24 | project leader (Carlos J. Ortiz) before putting in an excessive amount of work. 25 | 26 | **Do you have questions about the source code?** 27 | 28 | - Ask any question about how to use Paging in 29 | the [Paging discussion page](https://github.com/carlosjortiz/Paging/discussions/new?category=q-a). -------------------------------------------------------------------------------- /tests/Paging.Tests.Console/Tests/JsonSerializationTests.cs: -------------------------------------------------------------------------------- 1 | using Paging.Extensions; 2 | using Paging.Pagers; 3 | 4 | #if DOES_NOT_SUPPORT_JSON 5 | using Newtonsoft.Json; 6 | #else 7 | using System.Text.Json; 8 | #endif 9 | 10 | namespace Paging.Tests.Console.Tests; 11 | 12 | public static class JsonSerializationTests 13 | { 14 | public static void Test() 15 | { 16 | var list = new List { 17 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 18 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50 19 | }; 20 | var pager = new Pager(1, 10, 50); 21 | var page1 = list.Paginate(pager); 22 | var page2 = list.Paginate(2, 10); 23 | 24 | #if DOES_NOT_SUPPORT_JSON 25 | System.Console.WriteLine("Native Json not supported"); 26 | var json = JsonConvert.SerializeObject(page1); 27 | var json2 = JsonConvert.SerializeObject(page2); 28 | #else 29 | System.Console.WriteLine("Native Json supported"); 30 | var json = JsonSerializer.Serialize(page1); 31 | var json2 = JsonSerializer.Serialize(page2); 32 | #endif 33 | 34 | System.Console.WriteLine(json); 35 | System.Console.WriteLine(json2); 36 | 37 | #if DOES_NOT_SUPPORT_JSON 38 | var fromJson = JsonConvert.DeserializeObject>(json); 39 | #else 40 | var fromJson = JsonSerializer.Deserialize>(json); 41 | #endif 42 | 43 | System.Console.WriteLine(fromJson!.PageCount); 44 | System.Console.WriteLine(fromJson.Count); 45 | System.Console.WriteLine(fromJson.PageNumber); 46 | System.Console.WriteLine(fromJson.IsEmpty); 47 | System.Console.WriteLine(fromJson.PageSize); 48 | System.Console.WriteLine(fromJson.HasNextPage); 49 | System.Console.WriteLine(fromJson.HasPreviousPage); 50 | System.Console.WriteLine(fromJson.IsFirstPage); 51 | System.Console.WriteLine(fromJson.IsLastPage); 52 | System.Console.WriteLine(fromJson.TotalItemCount); 53 | 54 | foreach (var item in fromJson) 55 | { 56 | System.Console.WriteLine(item); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/Paging/Paging.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0;net7.0;net6.0;netstandard1.3;netstandard2.0;netstandard2.1 5 | enable 6 | enable 7 | default 8 | true 9 | true 10 | Carlos J. Ortiz 11 | # This library provides functionality for implementing paged lists. 12 | https://github.com/carlosjortiz/Paging 13 | true 14 | Pagination 15 | 16 | 2.0.1 17 | README.md 18 | git 19 | Qrtix.Paging 20 | 21 | - update docs 22 | 23 | 24 | MIT 25 | Pagination PaginatedList PagedList List Pager Paged Paging 26 | Copyright (c) Carlos J. Ortiz. All rights reserved. 27 | https://github.com/users/carlosjortiz/projects/1 28 | 29 | 30 | 31 | bin\Debug\Paging.xml 32 | 33 | 34 | 35 | bin\Release\Paging.xml 36 | 37 | 38 | 39 | SUPPORTS_JSON 40 | 41 | 42 | 43 | DOES_NOT_SUPPORT_JSON 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/Paging/Abstractions/IPager.cs: -------------------------------------------------------------------------------- 1 | namespace Paging.Abstractions; 2 | 3 | /// 4 | /// Represents a pager that handles pagination logic. 5 | /// 6 | public interface IPager 7 | { 8 | #region Properties 9 | 10 | /// 11 | /// Total number of datasets within the data-source. 12 | /// 13 | /// 14 | /// Total number of datasets within the data-source. 15 | /// 16 | int PageCount { get; } 17 | 18 | /// 19 | /// Total number of objects contained within the datasets. 20 | /// 21 | /// 22 | /// Total number of objects contained within the data-source. 23 | /// 24 | int TotalItemCount { get; } 25 | 26 | /// 27 | /// One-based index of this subset within the data-source, zero if the data-source is empty. 28 | /// 29 | /// 30 | /// One-based index of this subset within the data-source, zero if the data-source is empty. 31 | /// 32 | int PageNumber { get; } 33 | 34 | /// 35 | /// Maximum size any individual subset. 36 | /// 37 | /// 38 | /// Maximum size any individual subset. 39 | /// 40 | int PageSize { get; } 41 | 42 | /// 43 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 44 | /// is NOT the first subset within the data-source. 45 | /// 46 | /// 47 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 48 | /// is NOT the first subset within the data-source. 49 | /// 50 | bool HasPreviousPage { get; } 51 | 52 | /// 53 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 54 | /// is NOT the last subset within the data-source. 55 | /// 56 | /// 57 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 58 | /// is NOT the last subset within the data-source. 59 | /// 60 | bool HasNextPage { get; } 61 | 62 | /// 63 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 64 | /// is the first subset within the data-source. 65 | /// 66 | /// 67 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 68 | /// is the first subset within the data-source. 69 | /// 70 | bool IsFirstPage { get; } 71 | 72 | /// 73 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 74 | /// is the last subset within the data-source. 75 | /// 76 | /// 77 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 78 | /// is the last subset within the data-source. 79 | /// 80 | bool IsLastPage { get; } 81 | 82 | #endregion 83 | } -------------------------------------------------------------------------------- /.github/workflows/paging-cd.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a .NET project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net 3 | 4 | name: Paged List CD 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | 10 | env: 11 | PATH_PROJECT: src\Paging 12 | PATH_RELEASE: src\Paging\bin\Release 13 | PROJECT_NAME: Paging.csproj 14 | 15 | jobs: 16 | build: 17 | name: Build Paging project 18 | runs-on: windows-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Setup .NET 22 | uses: actions/setup-dotnet@v3 23 | with: 24 | dotnet-version: | 25 | 8.x 26 | 7.x 27 | 6.x 28 | 2.1.x 29 | 2.0.x 30 | # cache: true 31 | - name: Restore dependencies 32 | run: dotnet restore 33 | working-directory: ${{ env.PATH_PROJECT }} 34 | - name: Build 35 | run: dotnet build --no-restore --configuration Release 36 | working-directory: ${{ env.PATH_PROJECT }} 37 | - name: Create NuGet Package 38 | run: dotnet pack ${{ env.PROJECT_NAME }} --no-build -c Release 39 | working-directory: ${{ env.PATH_PROJECT }} 40 | - name: Upload Paging artifact 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: paging-lib 44 | path: . 45 | 46 | publish: 47 | name: Publish Paging lib 48 | runs-on: windows-latest 49 | needs: build 50 | steps: 51 | - name: Download Paging artifact 52 | uses: actions/download-artifact@v4 53 | with: 54 | name: paging-lib 55 | - name: Test artifact download 56 | run: ls -R 57 | - name: Publish into NuGet 58 | run: dotnet nuget push *.nupkg --skip-duplicate --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json 59 | working-directory: ${{ env.PATH_RELEASE }} 60 | - name: Publish into Github Packages 61 | env: 62 | GH_TOKEN: ${{ secrets.GH_TOKEN }} 63 | run: dotnet nuget push *.nupkg --skip-duplicate --api-key ${{ secrets.GH_TOKEN }} --source https://nuget.pkg.github.com/carlosjortiz/index.json 64 | working-directory: ${{ env.PATH_RELEASE }} 65 | # - name: Create Release in Github 66 | # env: 67 | # GH_TOKEN: ${{ secrets.GH_TOKEN }} 68 | # REPO_NAME: ${{ github.event.repository.name }} 69 | # run: | 70 | # $xml = [xml](Get-Content ${{env.PROJECT_NAME}}) 71 | # $version = $xml.Project.PropertyGroup.Version 72 | # $notes = $xml.Project.PropertyGroup.PackageReleaseNotes 73 | # $repo = $env:GITHUB_REPOSITORY 74 | # gh release create "v$version" -t "${{ env.REPO_NAME}}-v${version}" -n "${notes}" -R "$repo" 75 | # Compress-Archive -Path ".\bin\Release\*" -DestinationPath ".\bin\Release\${{ env.REPO_NAME }}-v${version}.zip" 76 | # gh release upload "v${version}" .\bin\Release\${{ env.REPO_NAME }}-v${version}.zip --clobber 77 | # working-directory: ${{ env.PATH_PROJECT }} -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | _layout: landing 3 | --- 4 | 5 | # Introduction 6 | 7 | ![NuGet Version](https://img.shields.io/nuget/v/Qrtix.Paging?logo=nuget) 8 | ![NuGet Downloads](https://img.shields.io/nuget/dt/Qrtix.Paging?style=flat&logo=nuget) 9 | ![GitHub Repo stars](https://img.shields.io/github/stars/carlosjortiz/Paging?style=flat&logo=github) 10 | 11 | The **Qrtix.Paging** library provides a robust solution for managing paginated collections of items in .NET 12 | applications. Pagination is a common requirement in software development, especially for applications dealing with large 13 | datasets or implementing user interfaces that display data in manageable chunks. 14 | 15 | This library introduces the `PagedList` class, which extends the functionality of the base `Pager` class and 16 | implements the `IPagedList` interface. This class encapsulates a paginated list of items along with essential 17 | pagination information such as total item count, current page number, and page size. 18 | 19 | By leveraging the `PagedList` class, developers can efficiently handle pagination logic, simplifying the process of 20 | fetching, navigating, and displaying data in a paginated manner. Whether it's rendering paginated data in web 21 | applications, API responses, or any other scenario where data needs to be chunked for better user experience or 22 | performance optimization, this library provides a versatile solution. 23 | 24 | Key features of the **Qrtix.Paging** library include: 25 | 26 | 1. **Flexible Pagination:** The library offers flexibility in configuring pagination parameters such as page size and 27 | current page number, enabling developers to adapt pagination to diverse use cases and application requirements. 28 | 2. **Efficient Data Retrieval:** With the ability to efficiently fetch items for the current page from a data source, the 29 | `PagedList` class optimizes data retrieval operations, ensuring optimal performance even with large datasets. 30 | 3. **Serialization Support:** The library supports JSON serialization, facilitating seamless integration with web APIs and 31 | serialization frameworks. Conditional compilation ensures compatibility with different JSON serialization libraries, 32 | enhancing interoperability across various development environments. 33 | 4. **Convenient Initialization:** Developers can easily create instances of the `PagedList` class using constructors 34 | tailored for different scenarios, including initialization from a data source or based on existing pagination 35 | information. 36 | 5. **Enhanced Usability:** The `PagedList` class exposes properties and methods for retrieving pagination metadata, 37 | iterating through items in the current page, and checking for emptiness, offering enhanced usability and versatility. 38 | 39 | In summary, the **Qrtix.Paging** library empowers developers to implement efficient pagination solutions in their .NET 40 | applications, streamlining the management and presentation of large datasets while maintaining flexibility and ease of 41 | use. Whether it's building scalable web applications, APIs, or data-driven interfaces, this library serves as a valuable 42 | tool for managing paginated data effectively. -------------------------------------------------------------------------------- /Paging.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{D7066DB3-706B-4D98-A0B1-D2553BCB61A2}" 4 | EndProject 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D7D757C5-E956-428C-80F7-09F391D9E9DE}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DCA445CD-81D1-461E-8066-923FB17477EB}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paging", "src\Paging\Paging.csproj", "{C85597B6-A4C7-4E0C-854B-66FD5F27F33B}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paging.Tests.UnitTests", "tests\Paging.Tests.UnitTests\Paging.Tests.UnitTests.csproj", "{A77C3D96-BA31-4032-B7E6-008EADFD9280}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paging.Benchmarks", "benchmarks\Paging.Benchmarks\Paging.Benchmarks.csproj", "{D797B4BE-4C73-42A2-908C-208CC1244967}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paging.Tests.Console", "tests\Paging.Tests.Console\Paging.Tests.Console.csproj", "{EE5EB4CB-4B2F-49B6-AFF9-3066D2F335EC}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Paging.Tests.WebApplication.Net8", "tests\Paging.Tests.WebApplication.Net8\Paging.Tests.WebApplication.Net8.csproj", "{F0777CC1-F809-4A46-8085-D418EA2DD14A}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(NestedProjects) = preSolution 25 | {C85597B6-A4C7-4E0C-854B-66FD5F27F33B} = {D7D757C5-E956-428C-80F7-09F391D9E9DE} 26 | {A77C3D96-BA31-4032-B7E6-008EADFD9280} = {DCA445CD-81D1-461E-8066-923FB17477EB} 27 | {D797B4BE-4C73-42A2-908C-208CC1244967} = {D7066DB3-706B-4D98-A0B1-D2553BCB61A2} 28 | {EE5EB4CB-4B2F-49B6-AFF9-3066D2F335EC} = {DCA445CD-81D1-461E-8066-923FB17477EB} 29 | {F0777CC1-F809-4A46-8085-D418EA2DD14A} = {DCA445CD-81D1-461E-8066-923FB17477EB} 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {C85597B6-A4C7-4E0C-854B-66FD5F27F33B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {C85597B6-A4C7-4E0C-854B-66FD5F27F33B}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {C85597B6-A4C7-4E0C-854B-66FD5F27F33B}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {C85597B6-A4C7-4E0C-854B-66FD5F27F33B}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {A77C3D96-BA31-4032-B7E6-008EADFD9280}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {A77C3D96-BA31-4032-B7E6-008EADFD9280}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {A77C3D96-BA31-4032-B7E6-008EADFD9280}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {A77C3D96-BA31-4032-B7E6-008EADFD9280}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {D797B4BE-4C73-42A2-908C-208CC1244967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {D797B4BE-4C73-42A2-908C-208CC1244967}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {D797B4BE-4C73-42A2-908C-208CC1244967}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {D797B4BE-4C73-42A2-908C-208CC1244967}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {EE5EB4CB-4B2F-49B6-AFF9-3066D2F335EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {EE5EB4CB-4B2F-49B6-AFF9-3066D2F335EC}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {EE5EB4CB-4B2F-49B6-AFF9-3066D2F335EC}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {EE5EB4CB-4B2F-49B6-AFF9-3066D2F335EC}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {F0777CC1-F809-4A46-8085-D418EA2DD14A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {F0777CC1-F809-4A46-8085-D418EA2DD14A}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {F0777CC1-F809-4A46-8085-D418EA2DD14A}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {F0777CC1-F809-4A46-8085-D418EA2DD14A}.Release|Any CPU.Build.0 = Release|Any CPU 52 | EndGlobalSection 53 | EndGlobal 54 | -------------------------------------------------------------------------------- /src/Paging/README.md: -------------------------------------------------------------------------------- 1 | # Paging Library 2 | 3 | ![NuGet Version](https://img.shields.io/nuget/v/Qrtix.Paging?logo=nuget) 4 | ![NuGet Downloads](https://img.shields.io/nuget/dt/Qrtix.Paging?style=flat&logo=nuget) 5 | ![GitHub Repo stars](https://img.shields.io/github/stars/carlosjortiz/Paging?style=flat&logo=github) 6 | 7 | This C# library provides functionality for implementing paged lists. 8 | 9 | Consult the online [documentation](https://carlosjortiz.github.io/Paging/) for more details. 10 | 11 | - [Getting Started](#getting-started) 12 | - [Using PagedList](#using-pagedlist) 13 | - [Contributing](#contributing) 14 | 15 | 16 | ## Getting Started 17 | 18 | Using the NuGet package manager console within Visual Studio run the following command: 19 | 20 | ``` 21 | Install-Package Qrtix.Paging 22 | ``` 23 | 24 | Or using the .NET Core CLI from a terminal window: 25 | 26 | ``` 27 | dotnet add package Qrtix.Paging 28 | ``` 29 | 30 | ## Using PagedList 31 | 32 | The `PagedList` class in the Qrtix.Paging library provides a convenient way to paginate large collections of data efficiently. Here's how you can use it: 33 | 34 | ### Instantiate a PagedList: 35 | 36 | ```csharp 37 | // Assuming you have a list of items named 'sourceList' and a page size of 10 38 | var pagedList = new PagedList(sourceList, pageNumber, pageSize); 39 | ``` 40 | 41 | ### Access Paginated Data: 42 | 43 | You can access the data in the paged list using indexers or enumeration: 44 | 45 | ```csharp 46 | // Accessing items using indexer 47 | var item = pagedList[index]; 48 | 49 | // Enumerating through the paged list 50 | foreach (var item in pagedList) 51 | { 52 | // Process each item 53 | } 54 | 55 | // Using for loop 56 | for (int i = 0; i < pagedList.Count; i++) 57 | { 58 | var item = pagedList[i]; 59 | // Process each item 60 | } 61 | ``` 62 | 63 | ### Retrieve Pagination Information: 64 | 65 | You can also retrieve pagination-related information such as the total number of pages, total items, etc.: 66 | 67 | ```csharp 68 | // Total number of items 69 | var totalItems = pagedList.TotalItemCount; 70 | 71 | // Total number of pages 72 | var totalPages = pagedList.PageCount; 73 | 74 | // Current page number 75 | var currentPage = pagedList.PageNumber; 76 | 77 | // Page size 78 | var pageSize = pagedList.PageSize; 79 | ``` 80 | 81 | ## Contributing 82 | 83 | **Did you find a bug?** 84 | 85 | - Ensure the bug was not already reported by searching on GitHub 86 | under [Issues](https://github.com/carlosjortiz/Paging/issues). 87 | - If you're unable to find an open issue addressing the 88 | problem, [open a new one](https://github.com/carlosjortiz/Paging/issues/new). Be sure to include a title and clear 89 | description, as much relevant information as possible, and a code sample or an executable test case demonstrating the 90 | expected behavior that is not occurring. 91 | 92 | **Did you write a patch that fixes a bug?** 93 | 94 | - Open a new GitHub pull request with the patch. 95 | - Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 96 | 97 | **Do you intend to add a new feature or change an existing one?** 98 | 99 | - First suggest your change in the [Paging ideas page](https://github.com/carlosjortiz/Paging/discussions/categories/ideas) 100 | for discussion. 101 | - There are no fixed rules on what should and shouldn't be in this library, but some features are more valuable than 102 | others, and some require long-term maintenance that outweighs the value of the feature. So please get sign-off from 103 | the 104 | project leader (Carlos J. Ortiz) before putting in an excessive amount of work. 105 | 106 | **Do you have questions about the source code?** 107 | 108 | - Ask any question about how to use Paging in 109 | the [Paging discussion page](https://github.com/carlosjortiz/Paging/discussions/new?category=q-a). -------------------------------------------------------------------------------- /src/Paging/Extensions/PagedListConstructorExtensions.cs: -------------------------------------------------------------------------------- 1 | using Paging.Abstractions; 2 | using Paging.Pagers; 3 | 4 | namespace Paging.Extensions; 5 | 6 | /// 7 | /// Provides extension methods for creating instances of the PagedList class. 8 | /// 9 | public static class PagedListConstructorExtensions 10 | { 11 | /// 12 | /// Create a new instance of the class with the provided , 13 | /// , and . 14 | /// Sets the total item count based on the count of the provided data source or to 0 if the data source is empty. 15 | /// Constructs the by retrieving the items for the specified page from the data source. 16 | /// 17 | /// The collection of items to paginate. 18 | /// The current page number. 19 | /// The number of items per page. 20 | /// Thrown when the is null. 21 | public static PagedList Paginate(this IEnumerable dataSource, int pageNumber, int pageSize) => new PagedList(dataSource, pageNumber, pageSize); 22 | 23 | /// 24 | /// Create a new instance of the class with the provided 25 | /// and . 26 | /// Constructs the paged list by retrieving the items for the specified page from the data source. 27 | /// 28 | /// The collection of items to paginate. 29 | /// The pager containing information about the current page, page size, etc. 30 | /// Thrown when the is null. 31 | public static PagedList Paginate(this IEnumerable dataSource, IPager pager) => new PagedList(dataSource, pager); 32 | 33 | /// 34 | /// Create a new instance of the class with the provided 35 | /// , , , and . 36 | /// Constructs the paged list by retrieving the items for the specified page from the data source and mapping them using the selector. 37 | /// 38 | /// The collection of items to paginate. 39 | /// The current page number. 40 | /// The number of items per page. 41 | /// The function to map items from the input type to the output type. 42 | /// The type of the items in the data source. 43 | /// The type of the items in the resulting paged list. 44 | /// A new instance of the class. 45 | /// Thrown when the is null. 46 | public static PagedList Paginate(this IEnumerable dataSource, int pageNumber, int pageSize, Func selector) 47 | => dataSource.Select(selector).Paginate(pageNumber, pageSize); 48 | 49 | /// 50 | /// Create a new instance of the class with the provided 51 | /// , , and . 52 | /// Constructs the paged list by retrieving the items for the specified page from the data source and mapping them using the selector. 53 | /// 54 | /// The collection of items to paginate. 55 | /// The pager containing information about the current page, page size, etc. 56 | /// The function to map items from the input type to the output type. 57 | /// The type of the items in the data source. 58 | /// The type of the items in the resulting paged list. 59 | /// A new instance of the class. 60 | /// Thrown when the is null. 61 | public static PagedList Paginate(this IEnumerable dataSource, IPager pager, Func selector) 62 | => dataSource.Select(selector).Paginate(pager); 63 | } 64 | -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/Pagers/PagerConstructorsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Paging.Pagers; 3 | 4 | namespace Paging.Tests.UnitTests.Pagers; 5 | 6 | public class PagerConstructorsTests 7 | { 8 | #region Happy Path 9 | 10 | // Constructor initializes PageNumber, PageSize, TotalItemCount, PageCount, HasPreviousPage, HasNextPage, IsFirstPage, IsLastPage 11 | [Fact] 12 | public void ParamConstructor_ParametersValid_CreatedSuccessfully() 13 | { 14 | // Arrange 15 | const int pageNumber = 1; 16 | const int pageSize = 10; 17 | const int totalItemCount = 100; 18 | 19 | // Act 20 | var pager = new Pager(pageNumber, pageSize, totalItemCount); 21 | 22 | // Assert 23 | pager.Should().NotBeNull(); 24 | pager.PageNumber.Should().Be(1); 25 | pager.PageSize.Should().Be(10); 26 | pager.TotalItemCount.Should().Be(totalItemCount); 27 | pager.PageCount.Should().Be(10); 28 | pager.HasPreviousPage.Should().BeFalse(); 29 | pager.HasNextPage.Should().BeTrue(); 30 | pager.IsFirstPage.Should().BeTrue(); 31 | pager.IsLastPage.Should().BeFalse(); 32 | } 33 | 34 | // PageCount is 0 and all other parameters are valid 35 | [Fact] 36 | public void ParamConstructor_PageCountIsZero_CreatedSuccessfully() 37 | { 38 | // Arrange 39 | const int pageNumber = 1; 40 | const int pageSize = 10; 41 | const int totalItemCount = 0; 42 | 43 | // Act 44 | var pager = new Pager(pageNumber, pageSize, totalItemCount); 45 | 46 | // Assert 47 | pager.PageCount.Should().Be(0); 48 | pager.PageNumber.Should().Be(pageNumber); 49 | pager.PageSize.Should().Be(pageSize); 50 | pager.TotalItemCount.Should().Be(totalItemCount); 51 | pager.HasPreviousPage.Should().BeFalse(); 52 | pager.HasNextPage.Should().BeFalse(); 53 | pager.IsFirstPage.Should().BeFalse(); 54 | pager.IsLastPage.Should().BeFalse(); 55 | } 56 | 57 | // TotalItemCount is 0 and all other parameters are valid 58 | [Fact] 59 | public void ParamConstructor_TotalItemCountIsZeroAndOtherParametersAreValid_CreatedSuccessfully() 60 | { 61 | // Arrange 62 | const int pageNumber = 1; 63 | const int pageSize = 10; 64 | const int totalItemCount = 0; 65 | 66 | // Act 67 | var pager = new Pager(pageNumber, pageSize, totalItemCount); 68 | 69 | // Assert 70 | pager.PageCount.Should().Be(0); 71 | pager.TotalItemCount.Should().Be(0); 72 | pager.PageNumber.Should().Be(1); 73 | pager.PageSize.Should().Be(10); 74 | pager.HasPreviousPage.Should().BeFalse(); 75 | pager.HasNextPage.Should().BeFalse(); 76 | pager.IsFirstPage.Should().BeFalse(); 77 | pager.IsLastPage.Should().BeFalse(); 78 | } 79 | 80 | [Fact] 81 | public void CopyConstructor_CreatedSuccessfully() 82 | { 83 | // Arrange 84 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 85 | 86 | // Act 87 | var copy = new Pager(pager); 88 | 89 | // Assert 90 | copy.Should().NotBeNull(); 91 | copy.PageNumber.Should().Be(pager.PageNumber); 92 | copy.PageSize.Should().Be(pager.PageSize); 93 | copy.TotalItemCount.Should().Be(pager.TotalItemCount); 94 | copy.PageCount.Should().Be(pager.PageCount); 95 | copy.HasPreviousPage.Should().Be(pager.HasPreviousPage); 96 | copy.HasNextPage.Should().Be(pager.HasNextPage); 97 | copy.IsFirstPage.Should().Be(pager.IsFirstPage); 98 | copy.IsLastPage.Should().Be(pager.IsLastPage); 99 | } 100 | 101 | [Fact] 102 | public void CopyConstructor_WithNewTotalItemCount_CreatedSuccessfully() 103 | { 104 | // Arrange 105 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 106 | 107 | // Act 108 | var copy = new Pager(pager, 25); 109 | 110 | // Assert 111 | copy.Should().NotBeNull(); 112 | copy.PageNumber.Should().Be(pager.PageNumber); 113 | copy.PageSize.Should().Be(pager.PageSize); 114 | copy.TotalItemCount.Should().Be(25); 115 | copy.PageCount.Should().Be(3); 116 | copy.HasPreviousPage.Should().Be(pager.HasPreviousPage); 117 | copy.HasNextPage.Should().Be(pager.HasNextPage); 118 | copy.IsFirstPage.Should().Be(pager.IsFirstPage); 119 | copy.IsLastPage.Should().Be(pager.IsLastPage); 120 | } 121 | 122 | #endregion 123 | 124 | #region Edge Cases 125 | 126 | // PageNumber is less than 1 127 | [Fact] 128 | public void ParamConstructor_PageNumberLessThan1_ThrowsArgumentOutOfRangeException() 129 | { 130 | // Arrange 131 | const int pageNumber = -1; 132 | const int pageSize = 10; 133 | const int totalItemCount = 50; 134 | 135 | // Act 136 | Action act = () => new Pager(pageNumber, pageSize, totalItemCount); 137 | 138 | // Assert 139 | act.Should().Throw() 140 | .WithMessage("pageNumber = -1. PageNumber cannot be below 1."); 141 | } 142 | 143 | // PageSize is less than 1 144 | [Fact] 145 | public void ParamConstructor_PageSizeLessThan1_ThrowsArgumentOutOfRangeException() 146 | { 147 | // Arrange 148 | const int pageNumber = 2; 149 | const int pageSize = -1; 150 | const int totalItemCount = 50; 151 | 152 | // Act 153 | Action act = () => new Pager(pageNumber, pageSize, totalItemCount); 154 | 155 | // Assert 156 | act.Should().Throw() 157 | .WithMessage("pageSize = -1. PageSize cannot be less than 1."); 158 | } 159 | 160 | // TotalItemCount is less than 0 161 | [Fact] 162 | public void ParamConstructor_TotalItemCountLessThan0_ThrowsArgumentOutOfRangeException() 163 | { 164 | // Arrange 165 | const int pageNumber = 2; 166 | const int pageSize = 10; 167 | const int totalItemCount = -1; 168 | 169 | // Act 170 | Action act = () => new Pager(pageNumber, pageSize, totalItemCount); 171 | 172 | // Assert 173 | act.Should().Throw() 174 | .WithMessage("totalItemCount = -1. TotalItemCount cannot be less than 0."); 175 | } 176 | 177 | [Fact] 178 | public void CopyConstructor_PagerNull_ThrowsNullReferenceException() 179 | { 180 | // Act 181 | Action act = () => new Pager(null); 182 | 183 | // Assert 184 | act.Should().Throw(); 185 | } 186 | 187 | #endregion 188 | } -------------------------------------------------------------------------------- /src/Paging/Pagers/Pager.cs: -------------------------------------------------------------------------------- 1 | using Paging.Abstractions; 2 | 3 | namespace Paging.Pagers; 4 | 5 | /// 6 | /// Represents a pager that handles pagination logic. 7 | /// 8 | public class Pager : IPager 9 | { 10 | #region Constructors 11 | 12 | /// 13 | /// Initializes a new instance of the class with the provided parameters. 14 | /// Calculates page-related attributes such as total page count and sets flags indicating the presence of previous, 15 | /// next, first, and last pages based on the provided page number, page size, and total item count. 16 | /// 17 | /// The current page number. 18 | /// The number of items per page. 19 | /// The total number of items across all pages. 20 | public Pager(int pageNumber, int pageSize, int totalItemCount) 21 | { 22 | Validator(pageNumber, pageSize, totalItemCount); 23 | 24 | PageNumber = pageNumber; 25 | PageSize = pageSize; 26 | TotalItemCount = totalItemCount; 27 | 28 | PageCount = TotalItemCount > 0 29 | ? (TotalItemCount + PageSize - 1) / PageSize 30 | : 0; 31 | 32 | var pageNumberIsValid = PageCount > 0 && PageNumber <= PageCount; 33 | 34 | HasPreviousPage = pageNumberIsValid && PageNumber > 1; 35 | HasNextPage = pageNumberIsValid && PageNumber < PageCount; 36 | IsFirstPage = pageNumberIsValid && PageNumber == 1; 37 | IsLastPage = pageNumberIsValid && PageNumber == PageCount; 38 | } 39 | 40 | /// 41 | /// Initializes a new instance of the class based on an existing pager instance with an optional update to the total item count. 42 | /// Constructs a new pager with the same page number, page size, and either the provided new total item count or the total item count of the source pager. 43 | /// 44 | /// The existing pager to create a new instance from. 45 | /// 46 | /// The optional new total item count. If provided, updates the total item count; 47 | /// otherwise, uses the total item count from the source pager. 48 | /// 49 | public Pager(IPager source, int? newTotalItemCount = null) 50 | : this(source.PageNumber, source.PageSize, newTotalItemCount ?? source.TotalItemCount) 51 | { 52 | } 53 | 54 | #endregion 55 | 56 | #region IPager Properties 57 | 58 | /// 59 | /// Total number of datasets within the data-source. 60 | /// 61 | /// 62 | /// Total number of datasets within the data-source. 63 | /// 64 | public int PageCount { get; } 65 | 66 | /// 67 | /// Total number of objects contained within the datasets. 68 | /// 69 | /// 70 | /// Total number of objects contained within the data-source. 71 | /// 72 | public int TotalItemCount { get; } 73 | 74 | /// 75 | /// One-based index of this dataset within the data-source, zero if the data-source is empty. 76 | /// 77 | /// 78 | /// One-based index of this dataset within the data-source, zero if the data-source is empty. 79 | /// 80 | public int PageNumber { get; private set; } 81 | 82 | /// 83 | /// Maximum size any individual dataset. 84 | /// 85 | /// 86 | /// Maximum size any individual dataset. 87 | /// 88 | public int PageSize { get; } 89 | 90 | /// 91 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 92 | /// is NOT the first dataset within the data-source. 93 | /// 94 | /// 95 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 96 | /// is NOT the first dataset within the data-source. 97 | /// 98 | public bool HasPreviousPage { get; private set; } 99 | 100 | /// 101 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 102 | /// is NOT the last dataset within the data-source. 103 | /// 104 | /// 105 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 106 | /// is NOT the last dataset within the data-source. 107 | /// 108 | public bool HasNextPage { get; private set; } 109 | 110 | /// 111 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 112 | /// is the first dataset within the data-source. 113 | /// 114 | /// 115 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 116 | /// is the first dataset within the data-source. 117 | /// 118 | public bool IsFirstPage { get; private set; } 119 | 120 | /// 121 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 122 | /// is the last dataset within the data-source. 123 | /// 124 | /// 125 | /// Returns true if the data-source is not empty and PageNumber is less than or equal to PageCount and this 126 | /// is the last dataset within the data-source. 127 | /// 128 | public bool IsLastPage { get; private set; } 129 | 130 | #endregion 131 | 132 | #region Private methods 133 | 134 | private static void Validator(int pageNumber, int pageSize, int totalItemCount) 135 | { 136 | if (pageNumber < 1) 137 | throw new ArgumentOutOfRangeException(null, $"pageNumber = {pageNumber}. PageNumber cannot be below 1."); 138 | 139 | if (pageSize < 1) 140 | throw new ArgumentOutOfRangeException(null, $"pageSize = {pageSize}. PageSize cannot be less than 1."); 141 | 142 | if (totalItemCount < 0) 143 | throw new ArgumentOutOfRangeException( 144 | null, 145 | $"totalItemCount = {totalItemCount}. TotalItemCount cannot be less than 0." 146 | ); 147 | } 148 | 149 | #endregion 150 | } -------------------------------------------------------------------------------- /tests/Paging.Tests.UnitTests/PagedCollections/PagedList/ConstructorsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Paging.Pagers; 3 | 4 | namespace Paging.Tests.UnitTests.PagedCollections.PagedList; 5 | 6 | public class ConstructorsTests 7 | { 8 | #region Happy Paths 9 | 10 | [Fact] 11 | public void ParamConstructor_UsingParametersValid_PagedListCreated() 12 | { 13 | // Arrange 14 | var list = Lab.DataSources.IntegerLists.Items50; 15 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 16 | 17 | // Act 18 | var pagedList = new PagedList(list, pager.PageNumber, pager.PageSize); 19 | 20 | // Assert 21 | pagedList.Should().NotBeNull(); 22 | pagedList.IsEmpty.Should().BeFalse(); 23 | pagedList.PageNumber.Should().Be(pager.PageNumber); 24 | pagedList.PageSize.Should().Be(pager.PageSize); 25 | pagedList.TotalItemCount.Should().Be(pager.TotalItemCount); 26 | pagedList.PageCount.Should().Be(pager.PageCount); 27 | pagedList.HasPreviousPage.Should().BeFalse(); 28 | pagedList.HasNextPage.Should().BeTrue(); 29 | pagedList.IsFirstPage.Should().BeTrue(); 30 | pagedList.IsLastPage.Should().BeFalse(); 31 | } 32 | 33 | [Fact] 34 | public void ParamConstructor_NonNullButEmptyDataSource_EmptyPagedListCreated() 35 | { 36 | // Arrange 37 | var emptyList = Lab.DataSources.IntegerLists.Empty; 38 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 39 | 40 | // Act 41 | var pagedList = new PagedList(emptyList, pager.PageNumber, pager.PageSize); 42 | 43 | // Assert 44 | pagedList.Should().NotBeNull(); 45 | pagedList.Count.Should().Be(0); 46 | pagedList.IsEmpty.Should().BeTrue(); 47 | pagedList.PageNumber.Should().Be(pager.PageNumber); 48 | pagedList.PageSize.Should().Be(pager.PageSize); 49 | pagedList.TotalItemCount.Should().Be(0); 50 | pagedList.PageCount.Should().Be(0); 51 | pagedList.HasPreviousPage.Should().BeFalse(); 52 | pagedList.HasNextPage.Should().BeFalse(); 53 | pagedList.IsFirstPage.Should().BeFalse(); 54 | pagedList.IsLastPage.Should().BeFalse(); 55 | } 56 | 57 | [Fact] 58 | public void ParamConstructor_UsingPagerValid_PagedListCreated() 59 | { 60 | // Arrange 61 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 62 | var list = Lab.DataSources.IntegerLists.Items50; 63 | 64 | // Act 65 | var pagedList = new PagedList(list, pager); 66 | 67 | // Assert 68 | pagedList.Should().NotBeNull(); 69 | pagedList.IsEmpty.Should().BeFalse(); 70 | pagedList.PageNumber.Should().Be(pager.PageNumber); 71 | pagedList.PageSize.Should().Be(pager.PageSize); 72 | pagedList.TotalItemCount.Should().Be(pager.TotalItemCount); 73 | pagedList.PageCount.Should().Be(pager.PageCount); 74 | pagedList.HasPreviousPage.Should().BeFalse(); 75 | pagedList.HasNextPage.Should().BeTrue(); 76 | pagedList.IsFirstPage.Should().BeTrue(); 77 | pagedList.IsLastPage.Should().BeFalse(); 78 | } 79 | 80 | [Fact] 81 | public void ParamConstructor_NonNullButEmptyDataSourceAndPagerValid_EmptyPagedListCreated() 82 | { 83 | // Arrange 84 | var emptyList = Lab.DataSources.IntegerLists.Empty; 85 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 86 | 87 | // Act 88 | var pagedList = new PagedList(emptyList, pager); 89 | 90 | // Assert 91 | pagedList.Should().NotBeNull(); 92 | pagedList.Count.Should().Be(0); 93 | pagedList.IsEmpty.Should().BeTrue(); 94 | pagedList.PageNumber.Should().Be(pager.PageNumber); 95 | pagedList.PageSize.Should().Be(pager.PageSize); 96 | pagedList.TotalItemCount.Should().Be(0); 97 | pagedList.PageCount.Should().Be(0); 98 | pagedList.HasPreviousPage.Should().BeFalse(); 99 | pagedList.HasNextPage.Should().BeFalse(); 100 | pagedList.IsFirstPage.Should().BeFalse(); 101 | pagedList.IsLastPage.Should().BeFalse(); 102 | } 103 | 104 | [Fact] 105 | public void EmptyConstructor_EmptyPagedListCreated() 106 | { 107 | // Act 108 | var emptyPagedList = PagedList.Empty(20); 109 | 110 | // Assert 111 | emptyPagedList.Should().NotBeNull(); 112 | emptyPagedList.Count.Should().Be(0); 113 | emptyPagedList.IsEmpty.Should().BeTrue(); 114 | emptyPagedList.PageNumber.Should().Be(1); 115 | emptyPagedList.PageSize.Should().Be(20); 116 | emptyPagedList.TotalItemCount.Should().Be(0); 117 | emptyPagedList.PageCount.Should().Be(0); 118 | emptyPagedList.HasPreviousPage.Should().BeFalse(); 119 | emptyPagedList.HasNextPage.Should().BeFalse(); 120 | emptyPagedList.IsFirstPage.Should().BeFalse(); 121 | emptyPagedList.IsLastPage.Should().BeFalse(); 122 | } 123 | 124 | [Fact] 125 | public void EmptyCopyConstructor_EmptyPagedListCreated() 126 | { 127 | // Act 128 | var emptyPagedList = PagedList.Empty(Lab.DataSources.Pagers.PageNumberFirst); 129 | 130 | // Assert 131 | emptyPagedList.Should().NotBeNull(); 132 | emptyPagedList.Count.Should().Be(0); 133 | emptyPagedList.IsEmpty.Should().BeTrue(); 134 | emptyPagedList.PageNumber.Should().Be(1); 135 | emptyPagedList.PageSize.Should().Be(Lab.DataSources.Pagers.PageNumberFirst.PageSize); 136 | emptyPagedList.TotalItemCount.Should().Be(0); 137 | emptyPagedList.PageCount.Should().Be(0); 138 | emptyPagedList.HasPreviousPage.Should().BeFalse(); 139 | emptyPagedList.HasNextPage.Should().BeFalse(); 140 | emptyPagedList.IsFirstPage.Should().BeFalse(); 141 | emptyPagedList.IsLastPage.Should().BeFalse(); 142 | } 143 | 144 | #endregion 145 | 146 | #region Edge Cases 147 | 148 | [Fact] 149 | public void ParamConstructor_WhenDataSourceIsNull_ThrowsArgumentNullException() 150 | { 151 | // Arrange 152 | List? dataSource = null; 153 | var pager = Lab.DataSources.Pagers.PageNumberFirst; 154 | 155 | // Act 156 | Action act = () => new PagedList(dataSource, pager.PageNumber, pager.PageSize); 157 | 158 | // Assert 159 | act.Should().Throw() 160 | .WithMessage("dataSource cannot be null. (Parameter 'dataSource')"); 161 | } 162 | 163 | [Fact] 164 | public void ParamConstructor_UsingAPagerValid_WhenDataSourceIsNull_ThrowsArgumentNullException() 165 | { 166 | // Arrange 167 | List? dataSource = null; 168 | 169 | // Act 170 | Action act = () => new PagedList(dataSource, Lab.DataSources.Pagers.PageNumberFirst); 171 | 172 | // Assert 173 | act.Should().Throw() 174 | .WithMessage("dataSource cannot be null. (Parameter 'dataSource')"); 175 | } 176 | 177 | #endregion 178 | } -------------------------------------------------------------------------------- /src/Paging/PagedList.cs: -------------------------------------------------------------------------------- 1 | #if DOES_NOT_SUPPORT_JSON 2 | using Newtonsoft.Json; 3 | #else 4 | using System.Text.Json.Serialization; 5 | #endif 6 | using Paging.Abstractions; 7 | using Paging.Pagers; 8 | using System.Collections; 9 | 10 | namespace Paging; 11 | 12 | /// 13 | /// Represents a paged list of items along with pagination information. 14 | /// 15 | /// The type of items in the paged list. 16 | public sealed class PagedList : Pager, IPagedList 17 | { 18 | #if DOES_NOT_SUPPORT_JSON 19 | [JsonProperty(propertyName:"Items")] 20 | #else 21 | [JsonInclude, JsonPropertyName("Items")] 22 | #endif 23 | private readonly T[] _dataset; 24 | 25 | #region Constructors 26 | 27 | /// 28 | /// Creates an empty instance of the class with the specified . 29 | /// 30 | /// The number of items per page. Defaults to 10 if not specified. 31 | /// An empty instance of the class with the given . 32 | public static PagedList Empty(int pageSize = 10) => new PagedList(Array.Empty(), 1, pageSize); 33 | 34 | /// 35 | /// Creates an empty instance of the class based on the pager information. 36 | /// Uses an empty data source and constructs the paged list with the pager's info. 37 | /// 38 | /// The pager containing information about the current page, page size, etc. 39 | /// An empty instance of the class based on the provided . 40 | public static PagedList Empty(IPager pager) => new PagedList(Array.Empty(), pager); 41 | 42 | /// 43 | /// Initializes a new instance of the class with the provided , 44 | /// , and . 45 | /// Sets the total item count based on the count of the provided data source or to 0 if the data source is empty. 46 | /// Constructs the by retrieving the items for the specified page from the data source. 47 | /// 48 | /// The collection of items to paginate. 49 | /// The current page number. 50 | /// The number of items per page. 51 | /// Thrown when the is null. 52 | public PagedList(IEnumerable dataSource, int pageNumber, int pageSize) 53 | : base(pageNumber, pageSize, dataSource?.Count() ?? 0) 54 | { 55 | if (dataSource == null) throw new ArgumentNullException(nameof(dataSource), "dataSource cannot be null."); 56 | var skip = (pageNumber - 1) * pageSize; 57 | 58 | _dataset = dataSource.Skip(skip).Take(pageSize).ToArray(); 59 | } 60 | 61 | /// 62 | /// Initializes a new instance of the class with the provided 63 | /// and . 64 | /// Constructs the paged list by retrieving the items for the specified page from the data source. 65 | /// 66 | /// The collection of items to paginate. 67 | /// The pager containing information about the current page, page size, etc. 68 | /// Thrown when the is null. 69 | public PagedList(IEnumerable dataSource, IPager pager) 70 | : this(dataSource, pager.PageNumber, pager.PageSize) 71 | { 72 | } 73 | 74 | [JsonConstructor] 75 | private PagedList(T[] _dataset, int pageNumber, int pageSize, int totalItemCount) 76 | : base(pageNumber, pageSize, totalItemCount) 77 | { 78 | this._dataset = _dataset ?? throw new ArgumentNullException(nameof(_dataset), "The item list cannot be null"); 79 | } 80 | 81 | #endregion 82 | 83 | #region IPagedList Properties 84 | 85 | /// 86 | /// Gets a value indicating whether the is empty based on the total item count. 87 | /// 88 | /// True if the has no items; otherwise, false. 89 | public bool IsEmpty => TotalItemCount == 0; 90 | 91 | /// 92 | /// Gets the count of items in the current page of the . 93 | /// 94 | /// The number of elements contained in the . 95 | public int Count => _dataset.Length; 96 | 97 | #endregion 98 | 99 | #region IPagedList implementation 100 | 101 | /// 102 | /// Retrieves a copied data from the current . 103 | /// 104 | /// A copied data from the current . 105 | public IPager GetPagerData() => new Pager(this); 106 | 107 | /// 108 | /// Returns an enumerator that iterates through the collection of items in the current page of the . 109 | /// 110 | /// A for the . 111 | public IEnumerator GetEnumerator() 112 | { 113 | return new Enumerator(this); 114 | } 115 | 116 | /// 117 | /// Gets the item at the specified index in the current page of the . 118 | /// 119 | /// The zero-based index of the item to get or set. 120 | /// The item at the specified index in the current page. 121 | public T this[int index] => _dataset[index]; 122 | 123 | #endregion 124 | 125 | #region Enumerator 126 | 127 | /// Enumerates the elements of a . 128 | public struct Enumerator : IEnumerator 129 | { 130 | #nullable disable 131 | private readonly PagedList _list; 132 | private int _index; 133 | 134 | internal Enumerator(PagedList list) 135 | { 136 | _list = list; 137 | _index = 0; 138 | Current = default(T); 139 | } 140 | 141 | /// Releases all resources used by the . 142 | public void Dispose() 143 | { 144 | } 145 | 146 | /// Advances the enumerator to the next element of the . 147 | /// The collection was modified after the enumerator was created. 148 | /// 149 | /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. 150 | public bool MoveNext() 151 | { 152 | var list = _list; 153 | 154 | if ((uint)_index >= (uint)list.PageCount) 155 | return MoveNextRare(); 156 | 157 | Current = list._dataset[_index]; 158 | ++_index; 159 | return true; 160 | } 161 | 162 | private bool MoveNextRare() 163 | { 164 | _index = _list.PageCount + 1; 165 | Current = default; 166 | return false; 167 | } 168 | 169 | #nullable enable 170 | /// Gets the element at the current position of the enumerator. 171 | /// The element in the at the current position of the enumerator. 172 | public T Current { get; private set; } 173 | 174 | /// Gets the element at the current position of the enumerator. 175 | /// The enumerator is positioned before the first element of the collection or after the last element. 176 | /// The element in the at the current position of the enumerator. 177 | object? IEnumerator.Current 178 | { 179 | get 180 | { 181 | if (_index == 0 || _index == _list.PageCount + 1) 182 | throw new InvalidOperationException("Invalid Operation Enumerator Operation Can't Happen"); 183 | return Current; 184 | } 185 | } 186 | 187 | /// Sets the enumerator to its initial position, which is before the first element in the collection. 188 | /// The collection was modified after the enumerator was created. 189 | void IEnumerator.Reset() 190 | { 191 | _index = 0; 192 | Current = default; 193 | } 194 | } 195 | 196 | #endregion 197 | } -------------------------------------------------------------------------------- /benchmarks/Paging.Benchmarks/Loops/IntsBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Paging.Extensions; 3 | using Paging.Tests.UnitTests; 4 | 5 | namespace Paging.Benchmarks.Loops; 6 | 7 | [MemoryDiagnoser(false)] 8 | public class IntsBenchmark 9 | { 10 | #region Short 11 | 12 | private static readonly int[] ArrayShort = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 13 | private static readonly List ListShort = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 14 | private static readonly IQueryable QueryShort = new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsQueryable(); 15 | private static readonly IEnumerable EnumerableShort = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 16 | 17 | private static readonly PagedList PagedListShort = 18 | Lab.DataSources.IntegerLists.Items50.Paginate(Lab.DataSources.Pagers.PageNumberFirst); 19 | 20 | 21 | #region While 22 | 23 | [Benchmark] 24 | public void While_ArrayShort() 25 | { 26 | var i = 0; 27 | while (i < ArrayShort.Length) 28 | { 29 | var item = ArrayShort[i++]; 30 | } 31 | } 32 | 33 | [Benchmark] 34 | public void While_ListShort() 35 | { 36 | var i = 0; 37 | while (i < ListShort.Count) 38 | { 39 | var item = ListShort[i++]; 40 | } 41 | } 42 | 43 | [Benchmark] 44 | public void While_QueryShort() 45 | { 46 | var i = 0; 47 | while (i < QueryShort.Count()) 48 | { 49 | var item = QueryShort.ElementAt(i++); 50 | } 51 | } 52 | 53 | [Benchmark] 54 | public void While_EnumerableShort() 55 | { 56 | var i = 0; 57 | while (i < EnumerableShort.Count()) 58 | { 59 | var item = EnumerableShort.ElementAt(i++); 60 | } 61 | } 62 | 63 | [Benchmark] 64 | public void While_PagedListShort() 65 | { 66 | var i = 0; 67 | while (i < PagedListShort.Count) 68 | { 69 | var item = PagedListShort[i++]; 70 | } 71 | } 72 | 73 | #endregion 74 | 75 | #region Do While 76 | 77 | [Benchmark] 78 | public void DoWhile_ArrayShort() 79 | { 80 | var i = 0; 81 | do 82 | { 83 | var item = ArrayShort[i++]; 84 | } while (i < ArrayShort.Length); 85 | } 86 | 87 | [Benchmark] 88 | public void DoWhile_ListShort() 89 | { 90 | var i = 0; 91 | do 92 | { 93 | var item = ListShort[i++]; 94 | } while (i < ListShort.Count); 95 | } 96 | 97 | [Benchmark] 98 | public void DoWhile_QueryShort() 99 | { 100 | var i = 0; 101 | do 102 | { 103 | var item = QueryShort.ElementAt(i++); 104 | } while (i < QueryShort.Count()); 105 | } 106 | 107 | [Benchmark] 108 | public void DoWhile_EnumerableShort() 109 | { 110 | var i = 0; 111 | do 112 | { 113 | var item = EnumerableShort.ElementAt(i++); 114 | } while (i < EnumerableShort.Count()); 115 | } 116 | 117 | [Benchmark] 118 | public void DoWhile_PagedListShort() 119 | { 120 | var i = 0; 121 | do 122 | { 123 | var item = PagedListShort[i++]; 124 | } while (i < PagedListShort.Count); 125 | } 126 | 127 | #endregion 128 | 129 | #region For 130 | 131 | [Benchmark] 132 | public void For_ArrayShort() 133 | { 134 | for (int i = 0; i < ArrayShort.Length; i++) 135 | { 136 | var item = ArrayShort[i]; 137 | } 138 | } 139 | 140 | [Benchmark] 141 | public void For_ListShort() 142 | { 143 | for (int i = 0; i < ListShort.Count; i++) 144 | { 145 | var item = ListShort[i]; 146 | } 147 | } 148 | 149 | [Benchmark] 150 | public void For_QueryShort() 151 | { 152 | for (int i = 0; i < QueryShort.Count(); i++) 153 | { 154 | var item = QueryShort.ElementAt(i); 155 | } 156 | } 157 | 158 | [Benchmark] 159 | public void For_EnumerableShort() 160 | { 161 | for (int i = 0; i < EnumerableShort.Count(); i++) 162 | { 163 | var item = EnumerableShort.ElementAt(i); 164 | } 165 | } 166 | 167 | [Benchmark] 168 | public void For_PagedListShort() 169 | { 170 | for (int i = 0; i < PagedListShort.Count; i++) 171 | { 172 | var item = PagedListShort[i]; 173 | } 174 | } 175 | 176 | #endregion 177 | 178 | #region For Each 179 | 180 | [Benchmark] 181 | public void ForEach_ArrayShort() 182 | { 183 | foreach (var item in ArrayShort) 184 | { 185 | var item2 = item; 186 | } 187 | } 188 | 189 | [Benchmark] 190 | public void ForEach_ListShort() 191 | { 192 | foreach (var item in ListShort) 193 | { 194 | var item2 = item; 195 | } 196 | } 197 | 198 | [Benchmark] 199 | public void ForEach_QueryShort() 200 | { 201 | foreach (var item in QueryShort) 202 | { 203 | var item2 = item; 204 | } 205 | } 206 | 207 | [Benchmark] 208 | public void ForEach_EnumerableShort() 209 | { 210 | foreach (var item in EnumerableShort) 211 | { 212 | var item2 = item; 213 | } 214 | } 215 | 216 | [Benchmark] 217 | public void ForEach_PagedListShort() 218 | { 219 | foreach (var item in PagedListShort) 220 | { 221 | var item2 = item; 222 | } 223 | } 224 | 225 | #endregion 226 | 227 | #region Linq 228 | 229 | [Benchmark] 230 | public void Linq_ArrayShort() 231 | { 232 | ArrayShort.Select(x => x); 233 | } 234 | 235 | [Benchmark] 236 | public void Linq_ListShort() 237 | { 238 | ListShort.Select(x => x); 239 | } 240 | 241 | [Benchmark] 242 | public void Linq_QueryShort() 243 | { 244 | QueryShort.Select(x => x); 245 | } 246 | 247 | [Benchmark] 248 | public void Linq_EnumerableShort() 249 | { 250 | EnumerableShort.Select(x => x); 251 | } 252 | 253 | [Benchmark] 254 | public void Linq_PagedListShort() 255 | { 256 | // PagedListShort.Select(x => x); 257 | } 258 | 259 | #endregion 260 | 261 | #endregion 262 | 263 | #region Long 264 | 265 | private static readonly IEnumerable EnumerableLong = Enumerable.Range(1, 100); 266 | private static readonly int[] ArrayLong = EnumerableLong.ToArray(); 267 | private static readonly List ListLong = EnumerableLong.ToList(); 268 | private static readonly IQueryable QueryLong = EnumerableLong.AsQueryable(); 269 | private static readonly PagedList PagedListLong = EnumerableLong.Paginate(1, 100); 270 | 271 | 272 | #region While 273 | 274 | [Benchmark] 275 | public void While_ArrayLong() 276 | { 277 | var i = 0; 278 | while (i < ArrayLong.Length) 279 | { 280 | var item = ArrayLong[i++]; 281 | } 282 | } 283 | 284 | [Benchmark] 285 | public void While_ListLong() 286 | { 287 | var i = 0; 288 | while (i < ListLong.Count) 289 | { 290 | var item = ListLong[i++]; 291 | } 292 | } 293 | 294 | [Benchmark] 295 | public void While_QueryLong() 296 | { 297 | var i = 0; 298 | while (i < QueryLong.Count()) 299 | { 300 | var item = QueryLong.ElementAt(i++); 301 | } 302 | } 303 | 304 | [Benchmark] 305 | public void While_EnumerableLong() 306 | { 307 | var i = 0; 308 | while (i < EnumerableLong.Count()) 309 | { 310 | var item = EnumerableLong.ElementAt(i++); 311 | } 312 | } 313 | 314 | [Benchmark] 315 | public void While_PagedListLong() 316 | { 317 | var i = 0; 318 | while (i < PagedListLong.Count) 319 | { 320 | var item = PagedListLong[i++]; 321 | } 322 | } 323 | 324 | #endregion 325 | 326 | #region Do While 327 | 328 | [Benchmark] 329 | public void DoWhile_ArrayLong() 330 | { 331 | var i = 0; 332 | do 333 | { 334 | var item = ArrayLong[i++]; 335 | } while (i < ArrayLong.Length); 336 | } 337 | 338 | [Benchmark] 339 | public void DoWhile_ListLong() 340 | { 341 | var i = 0; 342 | do 343 | { 344 | var item = ListLong[i++]; 345 | } while (i < ListLong.Count); 346 | } 347 | 348 | [Benchmark] 349 | public void DoWhile_QueryLong() 350 | { 351 | var i = 0; 352 | do 353 | { 354 | var item = QueryLong.ElementAt(i++); 355 | } while (i < QueryLong.Count()); 356 | } 357 | 358 | [Benchmark] 359 | public void DoWhile_EnumerableLong() 360 | { 361 | var i = 0; 362 | do 363 | { 364 | var item = EnumerableLong.ElementAt(i++); 365 | } while (i < EnumerableLong.Count()); 366 | } 367 | 368 | [Benchmark] 369 | public void DoWhile_PagedListLong() 370 | { 371 | var i = 0; 372 | do 373 | { 374 | var item = PagedListLong[i++]; 375 | } while (i < PagedListLong.Count); 376 | } 377 | 378 | #endregion 379 | 380 | #region For 381 | 382 | [Benchmark] 383 | public void For_ArrayLong() 384 | { 385 | for (int i = 0; i < ArrayLong.Length; i++) 386 | { 387 | var item = ArrayLong[i]; 388 | } 389 | } 390 | 391 | [Benchmark] 392 | public void For_ListLong() 393 | { 394 | for (int i = 0; i < ListLong.Count; i++) 395 | { 396 | var item = ListLong[i]; 397 | } 398 | } 399 | 400 | [Benchmark] 401 | public void For_QueryLong() 402 | { 403 | for (int i = 0; i < QueryLong.Count(); i++) 404 | { 405 | var item = QueryLong.ElementAt(i); 406 | } 407 | } 408 | 409 | [Benchmark] 410 | public void For_EnumerableLong() 411 | { 412 | for (int i = 0; i < EnumerableLong.Count(); i++) 413 | { 414 | var item = EnumerableLong.ElementAt(i); 415 | } 416 | } 417 | 418 | [Benchmark] 419 | public void For_PagedListLong() 420 | { 421 | for (int i = 0; i < PagedListLong.Count; i++) 422 | { 423 | var item = PagedListLong[i]; 424 | } 425 | } 426 | 427 | #endregion 428 | 429 | #region For Each 430 | 431 | [Benchmark] 432 | public void ForEach_ArrayLong() 433 | { 434 | foreach (var item in ArrayLong) 435 | { 436 | var item2 = item; 437 | } 438 | } 439 | 440 | [Benchmark] 441 | public void ForEach_ListLong() 442 | { 443 | foreach (var item in ListLong) 444 | { 445 | var item2 = item; 446 | } 447 | } 448 | 449 | [Benchmark] 450 | public void ForEach_QueryLong() 451 | { 452 | foreach (var item in QueryLong) 453 | { 454 | var item2 = item; 455 | } 456 | } 457 | 458 | [Benchmark] 459 | public void ForEach_EnumerableLong() 460 | { 461 | foreach (var item in EnumerableLong) 462 | { 463 | var item2 = item; 464 | } 465 | } 466 | 467 | [Benchmark] 468 | public void ForEach_PagedListLong() 469 | { 470 | foreach (var item in PagedListLong) 471 | { 472 | var item2 = item; 473 | } 474 | } 475 | 476 | #endregion 477 | 478 | #region Linq 479 | 480 | [Benchmark] 481 | public void Linq_ArrayLong() 482 | { 483 | ArrayLong.Select(x => x); 484 | } 485 | 486 | [Benchmark] 487 | public void Linq_ListLong() 488 | { 489 | ListLong.Select(x => x); 490 | } 491 | 492 | [Benchmark] 493 | public void Linq_QueryLong() 494 | { 495 | QueryLong.Select(x => x); 496 | } 497 | 498 | [Benchmark] 499 | public void Linq_EnumerableLong() 500 | { 501 | EnumerableLong.Select(x => x); 502 | } 503 | 504 | [Benchmark] 505 | public void Linq_PagedListLong() 506 | { 507 | // PagedListLong.Select(x => x); 508 | } 509 | 510 | #endregion 511 | 512 | #endregion 513 | } -------------------------------------------------------------------------------- /benchmarks/Paging.Benchmarks/Loops/StringBenchmark.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Paging.Extensions; 3 | using Paging.Tests.UnitTests; 4 | 5 | namespace Paging.Benchmarks.Loops; 6 | 7 | [MemoryDiagnoser(false)] 8 | public class StringBenchmark 9 | { 10 | #region Short 11 | 12 | private static readonly IEnumerable EnumerableShort = Enumerable.Range(1, 10).Select(x => x.ToString()); 13 | private static readonly string[] ArrayShort = EnumerableShort.ToArray(); 14 | private static readonly List ListShort = EnumerableShort.ToList(); 15 | private static readonly IQueryable QueryShort = EnumerableShort.AsQueryable(); 16 | 17 | private static readonly PagedList PagedListShort = 18 | EnumerableShort.Paginate(Lab.DataSources.Pagers.PageNumberFirst); 19 | 20 | 21 | #region While 22 | 23 | [Benchmark] 24 | public void While_ArrayShort() 25 | { 26 | var i = 0; 27 | while (i < ArrayShort.Length) 28 | { 29 | var item = ArrayShort[i++]; 30 | } 31 | } 32 | 33 | [Benchmark] 34 | public void While_ListShort() 35 | { 36 | var i = 0; 37 | while (i < ListShort.Count) 38 | { 39 | var item = ListShort[i++]; 40 | } 41 | } 42 | 43 | [Benchmark] 44 | public void While_QueryShort() 45 | { 46 | var i = 0; 47 | while (i < QueryShort.Count()) 48 | { 49 | var item = QueryShort.ElementAt(i++); 50 | } 51 | } 52 | 53 | [Benchmark] 54 | public void While_EnumerableShort() 55 | { 56 | var i = 0; 57 | while (i < EnumerableShort.Count()) 58 | { 59 | var item = EnumerableShort.ElementAt(i++); 60 | } 61 | } 62 | 63 | [Benchmark] 64 | public void While_PagedListShort() 65 | { 66 | var i = 0; 67 | while (i < PagedListShort.Count) 68 | { 69 | var item = PagedListShort[i++]; 70 | } 71 | } 72 | 73 | #endregion 74 | 75 | #region Do While 76 | 77 | [Benchmark] 78 | public void DoWhile_ArrayShort() 79 | { 80 | var i = 0; 81 | do 82 | { 83 | var item = ArrayShort[i++]; 84 | } while (i < ArrayShort.Length); 85 | } 86 | 87 | [Benchmark] 88 | public void DoWhile_ListShort() 89 | { 90 | var i = 0; 91 | do 92 | { 93 | var item = ListShort[i++]; 94 | } while (i < ListShort.Count); 95 | } 96 | 97 | [Benchmark] 98 | public void DoWhile_QueryShort() 99 | { 100 | var i = 0; 101 | do 102 | { 103 | var item = QueryShort.ElementAt(i++); 104 | } while (i < QueryShort.Count()); 105 | } 106 | 107 | [Benchmark] 108 | public void DoWhile_EnumerableShort() 109 | { 110 | var i = 0; 111 | do 112 | { 113 | var item = EnumerableShort.ElementAt(i++); 114 | } while (i < EnumerableShort.Count()); 115 | } 116 | 117 | [Benchmark] 118 | public void DoWhile_PagedListShort() 119 | { 120 | var i = 0; 121 | do 122 | { 123 | var item = PagedListShort[i++]; 124 | } while (i < PagedListShort.Count); 125 | } 126 | 127 | #endregion 128 | 129 | #region For 130 | 131 | [Benchmark] 132 | public void For_ArrayShort() 133 | { 134 | for (int i = 0; i < ArrayShort.Length; i++) 135 | { 136 | var item = ArrayShort[i]; 137 | } 138 | } 139 | 140 | [Benchmark] 141 | public void For_ListShort() 142 | { 143 | for (int i = 0; i < ListShort.Count; i++) 144 | { 145 | var item = ListShort[i]; 146 | } 147 | } 148 | 149 | [Benchmark] 150 | public void For_QueryShort() 151 | { 152 | for (int i = 0; i < QueryShort.Count(); i++) 153 | { 154 | var item = QueryShort.ElementAt(i); 155 | } 156 | } 157 | 158 | [Benchmark] 159 | public void For_EnumerableShort() 160 | { 161 | for (int i = 0; i < EnumerableShort.Count(); i++) 162 | { 163 | var item = EnumerableShort.ElementAt(i); 164 | } 165 | } 166 | 167 | [Benchmark] 168 | public void For_PagedListShort() 169 | { 170 | for (int i = 0; i < PagedListShort.Count; i++) 171 | { 172 | var item = PagedListShort[i]; 173 | } 174 | } 175 | 176 | #endregion 177 | 178 | #region For Each 179 | 180 | [Benchmark] 181 | public void ForEach_ArrayShort() 182 | { 183 | foreach (var item in ArrayShort) 184 | { 185 | var item2 = item; 186 | } 187 | } 188 | 189 | [Benchmark] 190 | public void ForEach_ListShort() 191 | { 192 | foreach (var item in ListShort) 193 | { 194 | var item2 = item; 195 | } 196 | } 197 | 198 | [Benchmark] 199 | public void ForEach_QueryShort() 200 | { 201 | foreach (var item in QueryShort) 202 | { 203 | var item2 = item; 204 | } 205 | } 206 | 207 | [Benchmark] 208 | public void ForEach_EnumerableShort() 209 | { 210 | foreach (var item in EnumerableShort) 211 | { 212 | var item2 = item; 213 | } 214 | } 215 | 216 | [Benchmark] 217 | public void ForEach_PagedListShort() 218 | { 219 | foreach (var item in PagedListShort) 220 | { 221 | var item2 = item; 222 | } 223 | } 224 | 225 | #endregion 226 | 227 | #region Linq 228 | 229 | [Benchmark] 230 | public void Linq_ArrayShort() 231 | { 232 | ArrayShort.Select(x => x); 233 | } 234 | 235 | [Benchmark] 236 | public void Linq_ListShort() 237 | { 238 | ListShort.Select(x => x); 239 | } 240 | 241 | [Benchmark] 242 | public void Linq_QueryShort() 243 | { 244 | QueryShort.Select(x => x); 245 | } 246 | 247 | [Benchmark] 248 | public void Linq_EnumerableShort() 249 | { 250 | EnumerableShort.Select(x => x); 251 | } 252 | 253 | [Benchmark] 254 | public void Linq_PagedListShort() 255 | { 256 | // PagedListShort.Select(x => x); 257 | } 258 | 259 | #endregion 260 | 261 | #endregion 262 | 263 | #region Long 264 | 265 | private static readonly IEnumerable EnumerableLong = Enumerable.Range(1, 100).Select(x => x.ToString()); 266 | private static readonly string[] ArrayLong = EnumerableLong.ToArray(); 267 | private static readonly List ListLong = EnumerableLong.ToList(); 268 | private static readonly IQueryable QueryLong = EnumerableLong.AsQueryable(); 269 | private static readonly PagedList PagedListLong = EnumerableLong.Paginate(1, 100); 270 | 271 | 272 | #region While 273 | 274 | [Benchmark] 275 | public void While_ArrayLong() 276 | { 277 | var i = 0; 278 | while (i < ArrayLong.Length) 279 | { 280 | var item = ArrayLong[i++]; 281 | } 282 | } 283 | 284 | [Benchmark] 285 | public void While_ListLong() 286 | { 287 | var i = 0; 288 | while (i < ListLong.Count) 289 | { 290 | var item = ListLong[i++]; 291 | } 292 | } 293 | 294 | [Benchmark] 295 | public void While_QueryLong() 296 | { 297 | var i = 0; 298 | while (i < QueryLong.Count()) 299 | { 300 | var item = QueryLong.ElementAt(i++); 301 | } 302 | } 303 | 304 | [Benchmark] 305 | public void While_EnumerableLong() 306 | { 307 | var i = 0; 308 | while (i < EnumerableLong.Count()) 309 | { 310 | var item = EnumerableLong.ElementAt(i++); 311 | } 312 | } 313 | 314 | [Benchmark] 315 | public void While_PagedListLong() 316 | { 317 | var i = 0; 318 | while (i < PagedListLong.Count) 319 | { 320 | var item = PagedListLong[i++]; 321 | } 322 | } 323 | 324 | #endregion 325 | 326 | #region Do While 327 | 328 | [Benchmark] 329 | public void DoWhile_ArrayLong() 330 | { 331 | var i = 0; 332 | do 333 | { 334 | var item = ArrayLong[i++]; 335 | } while (i < ArrayLong.Length); 336 | } 337 | 338 | [Benchmark] 339 | public void DoWhile_ListLong() 340 | { 341 | var i = 0; 342 | do 343 | { 344 | var item = ListLong[i++]; 345 | } while (i < ListLong.Count); 346 | } 347 | 348 | [Benchmark] 349 | public void DoWhile_QueryLong() 350 | { 351 | var i = 0; 352 | do 353 | { 354 | var item = QueryLong.ElementAt(i++); 355 | } while (i < QueryLong.Count()); 356 | } 357 | 358 | [Benchmark] 359 | public void DoWhile_EnumerableLong() 360 | { 361 | var i = 0; 362 | do 363 | { 364 | var item = EnumerableLong.ElementAt(i++); 365 | } while (i < EnumerableLong.Count()); 366 | } 367 | 368 | [Benchmark] 369 | public void DoWhile_PagedListLong() 370 | { 371 | var i = 0; 372 | do 373 | { 374 | var item = PagedListLong[i++]; 375 | } while (i < PagedListLong.Count); 376 | } 377 | 378 | #endregion 379 | 380 | #region For 381 | 382 | [Benchmark] 383 | public void For_ArrayLong() 384 | { 385 | for (int i = 0; i < ArrayLong.Length; i++) 386 | { 387 | var item = ArrayLong[i]; 388 | } 389 | } 390 | 391 | [Benchmark] 392 | public void For_ListLong() 393 | { 394 | for (int i = 0; i < ListLong.Count; i++) 395 | { 396 | var item = ListLong[i]; 397 | } 398 | } 399 | 400 | [Benchmark] 401 | public void For_QueryLong() 402 | { 403 | for (int i = 0; i < QueryLong.Count(); i++) 404 | { 405 | var item = QueryLong.ElementAt(i); 406 | } 407 | } 408 | 409 | [Benchmark] 410 | public void For_EnumerableLong() 411 | { 412 | for (int i = 0; i < EnumerableLong.Count(); i++) 413 | { 414 | var item = EnumerableLong.ElementAt(i); 415 | } 416 | } 417 | 418 | [Benchmark] 419 | public void For_PagedListLong() 420 | { 421 | for (int i = 0; i < PagedListLong.Count; i++) 422 | { 423 | var item = PagedListLong[i]; 424 | } 425 | } 426 | 427 | #endregion 428 | 429 | #region For Each 430 | 431 | [Benchmark] 432 | public void ForEach_ArrayLong() 433 | { 434 | foreach (var item in ArrayLong) 435 | { 436 | var item2 = item; 437 | } 438 | } 439 | 440 | [Benchmark] 441 | public void ForEach_ListLong() 442 | { 443 | foreach (var item in ListLong) 444 | { 445 | var item2 = item; 446 | } 447 | } 448 | 449 | [Benchmark] 450 | public void ForEach_QueryLong() 451 | { 452 | foreach (var item in QueryLong) 453 | { 454 | var item2 = item; 455 | } 456 | } 457 | 458 | [Benchmark] 459 | public void ForEach_EnumerableLong() 460 | { 461 | foreach (var item in EnumerableLong) 462 | { 463 | var item2 = item; 464 | } 465 | } 466 | 467 | [Benchmark] 468 | public void ForEach_PagedListLong() 469 | { 470 | foreach (var item in PagedListLong) 471 | { 472 | var item2 = item; 473 | } 474 | } 475 | 476 | #endregion 477 | 478 | #region Linq 479 | 480 | [Benchmark] 481 | public void Linq_ArrayLong() 482 | { 483 | ArrayLong.Select(x => x); 484 | } 485 | 486 | [Benchmark] 487 | public void Linq_ListLong() 488 | { 489 | ListLong.Select(x => x); 490 | } 491 | 492 | [Benchmark] 493 | public void Linq_QueryLong() 494 | { 495 | QueryLong.Select(x => x); 496 | } 497 | 498 | [Benchmark] 499 | public void Linq_EnumerableLong() 500 | { 501 | EnumerableLong.Select(x => x); 502 | } 503 | 504 | [Benchmark] 505 | public void Linq_PagedListLong() 506 | { 507 | // PagedListLong.Select(x => x); 508 | } 509 | 510 | #endregion 511 | 512 | #endregion 513 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paging Library 2 | 3 | ![NuGet Version](https://img.shields.io/nuget/v/Qrtix.Paging?logo=nuget) 4 | ![NuGet Downloads](https://img.shields.io/nuget/dt/Qrtix.Paging?style=flat&logo=nuget) 5 | ![GitHub Repo stars](https://img.shields.io/github/stars/carlosjortiz/Paging?style=flat&logo=github) 6 | 7 | - [Getting Started](#getting-started) 8 | - [Using PagedList](#using-pagedlist) 9 | - [Benchmark](#benchmark) 10 | - [Contributing](#contributing) 11 | 12 | This C# library provides functionality for implementing paged lists. 13 | 14 | Consult the online [documentation](https://carlosjortiz.github.io/Paging/) for more details. 15 | 16 | ## Getting Started 17 | 18 | Using the NuGet package manager console within Visual Studio run the following command: 19 | 20 | ``` 21 | Install-Package Qrtix.Paging 22 | ``` 23 | 24 | Or using the .NET Core CLI from a terminal window: 25 | 26 | ``` 27 | dotnet add package Qrtix.Paging 28 | ``` 29 | 30 | ## Using PagedList 31 | 32 | The `PagedList` class in the Qrtix.Paging library provides a convenient way to paginate large collections of data efficiently. Here's how you can use it: 33 | 34 | ### Instantiate a PagedList: 35 | 36 | ```csharp 37 | // Assuming you have a list of items named 'sourceList' and a page size of 10 38 | var pagedList = new PagedList(sourceList, pageNumber, pageSize); 39 | ``` 40 | 41 | ### Access Paginated Data: 42 | 43 | You can access the data in the paged list using indexers or enumeration: 44 | 45 | ```csharp 46 | // Accessing items using indexer 47 | var item = pagedList[index]; 48 | 49 | // Enumerating through the paged list 50 | foreach (var item in pagedList) 51 | { 52 | // Process each item 53 | } 54 | 55 | // Using for loop 56 | for (int i = 0; i < pagedList.Count; i++) 57 | { 58 | var item = pagedList[i]; 59 | // Process each item 60 | } 61 | ``` 62 | 63 | ### Retrieve Pagination Information: 64 | 65 | You can also retrieve pagination-related information such as the total number of pages, total items, etc.: 66 | 67 | ```csharp 68 | // Total number of items 69 | var totalItems = pagedList.TotalItemCount; 70 | 71 | // Total number of pages 72 | var totalPages = pagedList.PageCount; 73 | 74 | // Current page number 75 | var currentPage = pagedList.PageNumber; 76 | 77 | // Page size 78 | var pageSize = pagedList.PageSize; 79 | ``` 80 | 81 | ## Benchmark 82 | 83 | This benchmark evaluates the performance of different looping mechanisms across two scenarios: 84 | 85 | 1. **Short Collection:** This scenario involves collections with a smaller number of items. 86 | 2. **Long Collection:** This scenario includes collections with a larger number of items. 87 | 88 | Each scenario tests the following looping mechanisms: 89 | 90 | - `While` Loop 91 | - `Do While` Loop 92 | - `For` Loop 93 | - `ForEach` Loop 94 | - LINQ Operations 95 | 96 | ### Collection Types: 97 | 98 | For each looping mechanism, we tested against the following collection types: 99 | 100 | - **Array:** An array of integers. 101 | - **List:** A list of integers. 102 | - **Queryable:** A queryable collection of integers. 103 | - **Enumerable:** An enumerable collection of integers. 104 | - **PagedList:** A paginated list of integers using **Qrtix.Paging**. 105 | 106 | ### Summary 107 | 108 | BenchmarkDotNet v0.13.11, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) 109 | Intel Core i7-6560U CPU 2.20GHz (Skylake), 1 CPU, 4 logical and 2 physical cores 110 | .NET SDK 8.0.100 111 | [Host] : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 112 | DefaultJob : .NET 8.0.1 (8.0.123.58001), X64 RyuJIT AVX2 113 | 114 | 115 | **Short Collection** 116 | 117 | | Method | Mean | Error | StdDev | Median | Allocated | 118 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 119 | | While_ArrayShort | 7.6639 ns | 0.1519 ns | 0.4080 ns | 7.6199 ns | - | 120 | | While_ListShort | 8.4653 ns | 0.2245 ns | 0.6550 ns | 8.1672 ns | - | 121 | | While_QueryShort | 3,431,964.0625 ns | 45,744.4657 ns | 40,551.2773 ns | 3,430,451.5625 ns | 129692 B | 122 | | While_EnumerableShort | 73.1155 ns | 1.3762 ns | 1.0745 ns | 73.0276 ns | - | 123 | | While_PagedListShort | 5.5109 ns | 0.1528 ns | 0.1430 ns | 5.5412 ns | - | 124 | 125 | | Method | Mean | Error | StdDev | Median | Allocated | 126 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 127 | | DoWhile_ArrayShort | 4.3652 ns | 0.0768 ns | 0.0719 ns | 4.3687 ns | - | 128 | | DoWhile_ListShort | 11.3414 ns | 0.2526 ns | 0.2363 ns | 11.3171 ns | - | 129 | | DoWhile_QueryShort | 3,277,667.6339 ns | 35,518.4877 ns | 31,486.2142 ns | 3,270,392.1875 ns | 123695 B | 130 | | DoWhile_EnumerableShort | 66.0196 ns | 1.3358 ns | 1.1841 ns | 65.5695 ns | - | 131 | | DoWhile_PagedListShort | 5.5464 ns | 0.2091 ns | 0.5933 ns | 5.2521 ns | - | 132 | 133 | | Method | Mean | Error | StdDev | Median | Allocated | 134 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 135 | | For_ArrayShort | 5.1097 ns | 0.1151 ns | 0.0961 ns | 5.0841 ns | - | 136 | | For_ListShort | 7.5736 ns | 0.1151 ns | 0.1077 ns | 7.5537 ns | - | 137 | | For_QueryShort | 3,459,728.4375 ns | 48,145.8848 ns | 45,035.6905 ns | 3,449,319.5312 ns | 129695 B | 138 | | For_EnumerableShort | 78.2072 ns | 1.4956 ns | 1.3990 ns | 77.7426 ns | - | 139 | | For_PagedListShort | 5.2608 ns | 0.1488 ns | 0.1528 ns | 5.2277 ns | - | 140 | 141 | | Method | Mean | Error | StdDev | Median | Allocated | 142 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 143 | | ForEach_ArrayShort | 4.0271 ns | 0.0907 ns | 0.0848 ns | 4.0092 ns | - | 144 | | ForEach_ListShort | 8.2977 ns | 0.2083 ns | 0.2708 ns | 8.2323 ns | - | 145 | | ForEach_QueryShort | 39.6814 ns | 0.5177 ns | 0.4843 ns | 39.5451 ns | 40 B | 146 | | ForEach_EnumerableShort | 34.2966 ns | 0.3514 ns | 0.3287 ns | 34.2952 ns | 32 B | 147 | | ForEach_PagedListShort | 21.7900 ns | 0.3200 ns | 0.3143 ns | 21.7952 ns | 32 B | 148 | 149 | | Method | Mean | Error | StdDev | Median | Allocated | 150 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 151 | | Linq_ArrayShort | 30.8896 ns | 0.6668 ns | 0.6237 ns | 31.0211 ns | 48 B | 152 | | Linq_ListShort | 33.7396 ns | 0.6790 ns | 0.6351 ns | 33.8632 ns | 72 B | 153 | | Linq_QueryShort | 1,348.1288 ns | 18.9453 ns | 21.0577 ns | 1,342.7159 ns | 744 B | 154 | | Linq_EnumerableShort | 33.2310 ns | 0.6789 ns | 0.6351 ns | 33.3771 ns | 56 B | 155 | | Linq_PagedListShort | 0.0278 ns | 0.0370 ns | 0.0346 ns | 0.0046 ns | - | 156 | 157 | 158 | **Long Collection** 159 | 160 | | Method | Mean | Error | StdDev | Median | Allocated | 161 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 162 | | While_ArrayLong | 56.2922 ns | 0.4045 ns | 0.3586 ns | 56.2651 ns | - | 163 | | While_ListLong | 81.7122 ns | 1.0123 ns | 0.9469 ns | 81.7052 ns | - | 164 | | While_QueryLong | 22,869,968.3036 ns | 207,503.6075 ns | 183,946.5432 ns | 22,858,203.1250 ns | 1251498 B | 165 | | While_EnumerableLong | 912.5066 ns | 13.0414 ns | 12.1989 ns | 910.0410 ns | - | 166 | | While_PagedListLong | 66.1971 ns | 1.1316 ns | 1.0031 ns | 66.0343 ns | - | 167 | 168 | | Method | Mean | Error | StdDev | Median | Allocated | 169 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 170 | | DoWhile_ArrayLong | 57.2344 ns | 1.0759 ns | 1.4363 ns | 57.1936 ns | - | 171 | | DoWhile_ListLong | 120.3313 ns | 2.4316 ns | 3.4873 ns | 119.1208 ns | - | 172 | | DoWhile_QueryLong | 22,660,970.4327 ns | 238,217.8882 ns | 198,922.7256 ns | 22,667,150.0000 ns | 1245457 B | 173 | | DoWhile_EnumerableLong | 860.3039 ns | 13.9846 ns | 13.0812 ns | 856.9570 ns | - | 174 | | DoWhile_PagedListLong | 65.1459 ns | 0.6407 ns | 0.5350 ns | 65.1131 ns | - | 175 | 176 | | Method | Mean | Error | StdDev | Median | Allocated | 177 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 178 | | For_ArrayLong | 56.9101 ns | 0.9059 ns | 1.7882 ns | 56.2549 ns | - | 179 | | For_ListLong | 81.8885 ns | 0.9291 ns | 0.8690 ns | 82.0507 ns | - | 180 | | For_QueryLong | 22,776,829.4271 ns | 255,467.1461 ns | 199,451.9438 ns | 22,809,110.9375 ns | 1251498 B | 181 | | For_EnumerableLong | 861.3526 ns | 11.2557 ns | 9.9779 ns | 860.9481 ns | - | 182 | | For_PagedListLong | 63.6049 ns | 0.6723 ns | 0.6289 ns | 63.5547 ns | - | 183 | 184 | | Method | Mean | Error | StdDev | Median | Allocated | 185 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 186 | | ForEach_ArrayLong | 47.0325 ns | 0.6834 ns | 0.6393 ns | 47.0002 ns | - | 187 | | ForEach_ListLong | 84.5413 ns | 0.9743 ns | 0.8637 ns | 84.4462 ns | - | 188 | | ForEach_QueryLong | 187.4379 ns | 1.6932 ns | 1.5838 ns | 187.5235 ns | 40 B | 189 | | ForEach_EnumerableLong | 242.0037 ns | 3.2134 ns | 3.0058 ns | 240.6808 ns | 40 B | 190 | | ForEach_PagedListLong | 12.5111 ns | 0.2759 ns | 0.3177 ns | 12.4391 ns | 32 B | 191 | 192 | | Method | Mean | Error | StdDev | Median | Allocated | 193 | |------------------------ |-------------------:|----------------:|----------------:|-------------------:|----------:| 194 | | Linq_ArrayLong | 31.6275 ns | 0.6781 ns | 0.6660 ns | 31.7416 ns | 48 B | 195 | | Linq_ListLong | 29.8407 ns | 0.6393 ns | 0.6840 ns | 29.8014 ns | 72 B | 196 | | Linq_QueryLong | 1,370.2819 ns | 27.2387 ns | 46.9855 ns | 1,357.7888 ns | 744 B | 197 | | Linq_EnumerableLong | 28.5803 ns | 0.5914 ns | 0.5532 ns | 28.4244 ns | 48 B | 198 | | Linq_PagedListLong | 0.0222 ns | 0.0273 ns | 0.0303 ns | 0.0096 ns | - | 199 | 200 | 201 | 202 | ## Contributing 203 | 204 | If you would like to get involved with this project, please first read the [Contribution Guidelines](docs/docs/contributiing.md) --------------------------------------------------------------------------------