Skip to content

Implement async enumerators and enhance related documentation and tests - #37

Merged
GabrielMarquezMatte merged 3 commits into
masterfrom
develop
Jul 21, 2026
Merged

Implement async enumerators and enhance related documentation and tests#37
GabrielMarquezMatte merged 3 commits into
masterfrom
develop

Conversation

@GabrielMarquezMatte

Copy link
Copy Markdown
Owner

This pull request introduces comprehensive support for async enumeration (await foreach) of rows and strongly-typed models, including for ref struct types, across all supported Excel formats (XLS, XLSX, XLSB, CSV). The changes unify and modernize both the API surface and documentation, making async consumption of rows and zero-allocation parsing with ref struct models both easier and more idiomatic. The implementation also refactors enumerator types to use reference types where required for async state management, and clarifies usage limitations and patterns in the documentation.

Async Enumeration Support and API Changes

  • All row readers (CsvReader, XlsReader, XlsbReader) now implement a parameterless GetAsyncEnumerator() method, enabling direct use with await foreach for both row and strongly-typed model enumeration. Interface IExcelRowReader<TEnumerator> is updated to include this method. [1] [2] [3] [4]
  • NamedRefRowEnumerable<TModel, TEnumerator> adds both GetAsyncEnumerator() for pattern-based async enumeration and GetAsyncEnumeratorAsync() for explicit async opening with a cancellation token, supporting advanced scenarios.

Enumerator Implementation Changes

  • NamedRefRowEnumerator<TModel, TEnumerator> is refactored from a struct to a sealed class to correctly manage async state across awaits, ensuring correct behavior for both sync and async enumeration of ref struct models.
  • Adds an efficient, pattern-based MoveNextAsync() implementation to NamedRefRowEnumerator, along with proper async disposal support (DisposeAsync). [1] [2]

Documentation Updates

  • The README.md is updated to document the new await foreach support for all readers and for strongly-typed ref struct parsing, clarifying usage patterns, limitations, and best practices. This includes code examples and explanations of why certain interfaces (like IAsyncEnumerable<T>) are not used for ref struct models. [1] [2] [3] [4]

Code Quality and Suppression

  • Adds appropriate code analysis suppression attributes to methods where reference-type enumerators are required (for async state or language limitations), and to clarify ownership and disposal responsibilities. [1] [2]

These changes provide a consistent, modern async API for Excel file reading, support high-performance zero-allocation parsing, and clarify usage for both sync and async consumers.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 50.00000% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.45%. Comparing base (ba6b932) to head (08feff8).

Files with missing lines Patch % Lines
...ader.Core/Parser/Internal/NamedRefRowEnumerator.cs 34.78% 12 Missing and 3 partials ⚠️
...ader.Core/Parser/Internal/NamedRefRowEnumerable.cs 50.00% 2 Missing ⚠️
.../ExcelReader.Core/Writer/Internal/CellFormatter.cs 0.00% 0 Missing and 1 partial ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #37      +/-   ##
==========================================
- Coverage   88.66%   88.45%   -0.21%     
==========================================
  Files          88       88              
  Lines        5600     5639      +39     
  Branches     1067     1071       +4     
==========================================
+ Hits         4965     4988      +23     
- Misses        405      419      +14     
- Partials      230      232       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown

Benchmark Results

Measured on ubuntu-latest (GitHub Actions). Runner noise may affect absolute numbers; use these for relative comparisons within a PR.

ExcelReader.Benchmarks.ColdStartBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.70GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-GOEFXF : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

InvocationCount=1  IterationCount=1  LaunchCount=16  
RunStrategy=ColdStart  UnrollFactor=1  WarmupCount=0  

Method Rows Mean Error StdDev Allocated
TypedParseFirstUse 200 48.56 ms 7.534 ms 7.399 ms 29.55 KB
RecordWriteFirstUse 200 24.99 ms 0.269 ms 0.264 ms 79.41 KB

ExcelReader.Benchmarks.CsvParseBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
INTEL XEON PLATINUM 8573C 3.20GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v4
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v4

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
ExcelParserSync 50000 6.185 ms 0.4631 ms 0.0717 ms 1.00 0.01 46.8750 - 3.86 MB 1.00
ExcelParserAsync 50000 6.981 ms 0.0773 ms 0.0120 ms 1.13 0.01 46.8750 - 3.86 MB 1.00
Sep 50000 9.468 ms 0.2762 ms 0.0717 ms 1.53 0.02 46.8750 - 3.87 MB 1.00
Sylvan 50000 13.762 ms 0.4012 ms 0.0621 ms 2.23 0.02 125.0000 15.6250 10.95 MB 2.84
CsvHelperLib 50000 26.520 ms 1.2457 ms 0.1928 ms 4.29 0.05 156.2500 - 14.41 MB 3.73

ExcelReader.Benchmarks.CsvReadBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
ExcelReader 50000 5.382 ms 0.0071 ms 0.0011 ms 1.00 0.00 - - 232 B 1.00
ExcelReaderAsync 50000 5.416 ms 0.0090 ms 0.0014 ms 1.01 0.00 - - 352 B 1.52
Sep 50000 11.911 ms 0.0420 ms 0.0065 ms 2.21 0.00 - - 4024 B 17.34
Sylvan 50000 6.104 ms 0.0248 ms 0.0038 ms 1.13 0.00 93.7500 7.8125 1688701 B 7,278.88
CsvHelperLib 50000 33.421 ms 0.6621 ms 0.1025 ms 6.21 0.02 933.3333 66.6667 16273424 B 70,144.07

ExcelReader.Benchmarks.CsvWriteBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
ExcelReaderWriter 50000 9.368 ms 0.0687 ms 0.0106 ms 1.00 500.0000 500.0000 500.0000 4 MB 1.00
Sep 50000 10.362 ms 0.4583 ms 0.1190 ms 1.11 500.0000 500.0000 500.0000 4.01 MB 1.00
SylvanWriter 50000 10.100 ms 0.0465 ms 0.0121 ms 1.08 500.0000 500.0000 500.0000 4.04 MB 1.01
CsvHelperLib 50000 19.957 ms 0.6310 ms 0.0976 ms 2.13 1187.5000 656.2500 593.7500 13.79 MB 3.44

ExcelReader.Benchmarks.ParseBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
INTEL XEON PLATINUM 8573C 3.20GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v4
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v4

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
ExcelParserSync 50000 16.620 ms 0.3254 ms 0.0845 ms 1.00 0.01 31.2500 3968.19 KB 1.000
ExcelParserStructSync 50000 16.711 ms 0.4538 ms 0.1179 ms 1.01 0.01 - 1624.46 KB 0.409
RefParserParseNamedSync 50000 15.215 ms 0.9461 ms 0.2457 ms 0.92 0.01 - 13.06 KB 0.003
ExcelParserAsync 50000 17.228 ms 0.4504 ms 0.1170 ms 1.04 0.01 31.2500 3970.34 KB 1.001
ExcelParserXlsbSync 50000 8.219 ms 0.1481 ms 0.0229 ms 0.49 0.00 46.8750 3969.92 KB 1.000
ExcelParserXlsbAsync 50000 8.410 ms 0.1694 ms 0.0262 ms 0.51 0.00 46.8750 3972.49 KB 1.001
MiniExcel 50000 188.245 ms 49.5112 ms 12.8579 ms 11.33 0.71 2000.0000 202523.26 KB 51.037
Sylvan 50000 66.367 ms 0.9002 ms 0.2338 ms 3.99 0.02 125.0000 10726.41 KB 2.703
SylvanAsync 50000 70.558 ms 5.9780 ms 1.5525 ms 4.25 0.09 125.0000 10727.38 KB 2.703

ExcelReader.Benchmarks.ReadBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Allocated Alloc Ratio
ExcelReader 50000 16.315 ms 0.0287 ms 0.0044 ms 1.00 0.00 - 12.59 KB 1.00
ExcelReaderAsync 50000 16.722 ms 0.0259 ms 0.0067 ms 1.02 0.00 - 14.72 KB 1.17
ExcelReaderXlsb 50000 6.427 ms 0.0414 ms 0.0108 ms 0.39 0.00 - 14.32 KB 1.14
ExcelReaderXlsbAsync 50000 6.594 ms 0.0208 ms 0.0032 ms 0.40 0.00 - 16.88 KB 1.34
MiniExcel 50000 208.224 ms 20.4385 ms 5.3078 ms 12.76 0.30 13000.0000 214012.34 KB 17,004.08
Sylvan 50000 53.361 ms 2.4077 ms 0.6253 ms 3.27 0.04 100.0000 1939.18 KB 154.08

ExcelReader.Benchmarks.RealDataReadBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Allocated Alloc Ratio
Xlsx_ExcelReader 94.093 ms 1.6726 ms 0.4344 ms 1.00 0.01 - - 35008 B 1.000
Xlsx_Sylvan 311.663 ms 18.2563 ms 4.7411 ms 3.31 0.05 - - 659568 B 18.840
Xlsm_ExcelReader 94.040 ms 2.4720 ms 0.6420 ms 1.00 0.01 - - 35048 B 1.001
Xlsm_Sylvan 315.388 ms 3.8590 ms 1.0022 ms 3.35 0.02 - - 659648 B 18.843
Xlsb_ExcelReader 32.369 ms 0.2504 ms 0.0650 ms 0.34 0.00 - - 41640 B 1.189
Xlsb_Sylvan 45.463 ms 3.0234 ms 0.7852 ms 0.48 0.01 - - 346675 B 9.903
Xls_ExcelReader 19.558 ms 0.1347 ms 0.0350 ms 0.21 0.00 - - 10208 B 0.292
Xls_Sylvan 27.171 ms 0.1340 ms 0.0207 ms 0.29 0.00 - - 190366 B 5.438
Csv_ExcelReader 9.100 ms 0.0540 ms 0.0140 ms 0.10 0.00 - - 232 B 0.007
Csv_Sylvan 17.062 ms 0.9742 ms 0.1508 ms 0.18 0.00 2218.7500 125.0000 37484040 B 1,070.728

ExcelReader.Benchmarks.RecordWriteBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
Xlsx 50000 21.470 ms 0.2825 ms 0.0734 ms 1.00 500.0000 500.0000 500.0000 4.02 MB 1.00
Xlsb 50000 10.634 ms 0.1343 ms 0.0349 ms 0.50 500.0000 500.0000 500.0000 4.02 MB 1.00
Xls 50000 7.434 ms 0.1273 ms 0.0197 ms 0.35 273.4375 273.4375 273.4375 4.03 MB 1.00
Csv 50000 10.497 ms 0.0638 ms 0.0099 ms 0.49 500.0000 500.0000 500.0000 4 MB 1.00

ExcelReader.Benchmarks.WriteBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
ExcelReaderWriter 50000 20.354 ms 0.2814 ms 0.0731 ms 1.00 0.00 500.0000 500.0000 500.0000 4.02 MB 1.00
ExcelReaderWriterSharedStrings 50000 20.340 ms 0.0759 ms 0.0197 ms 1.00 0.00 500.0000 500.0000 500.0000 4.06 MB 1.01
ExcelReaderXlsbWriter 50000 10.242 ms 0.0471 ms 0.0073 ms 0.50 0.00 500.0000 500.0000 500.0000 4.02 MB 1.00
ExcelReaderXlsbWriterSharedStrings 50000 9.961 ms 0.1142 ms 0.0296 ms 0.49 0.00 500.0000 500.0000 500.0000 4.06 MB 1.01
MiniExcel 50000 100.314 ms 3.1122 ms 0.4816 ms 4.93 0.03 5000.0000 1000.0000 1000.0000 84.89 MB 21.13
SpreadCheetah 50000 20.973 ms 0.7370 ms 0.1914 ms 1.03 0.01 1437.5000 718.7500 718.7500 15.84 MB 3.94

ExcelReader.Benchmarks.XlsReadBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio Gen0 Gen1 Allocated Alloc Ratio
ExcelReader 50000 6.374 ms 0.0272 ms 0.0071 ms 1.00 - - 2.89 KB 1.00
ExcelReaderAsync 50000 6.517 ms 0.0157 ms 0.0024 ms 1.02 - - 2.96 KB 1.02
Sylvan 50000 8.135 ms 0.1316 ms 0.0204 ms 1.28 93.7500 15.6250 1717.73 KB 594.24

ExcelReader.Benchmarks.XlsWriteBenchmark


BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
  [Host]     : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3
  Job-MEHJPP : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

IterationCount=5  WarmupCount=1  

Method Rows Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
XlsWriter 50000 7.568 ms 0.1218 ms 0.0316 ms 1.00 492.1875 492.1875 492.1875 16.03 MB 1.00
XlsxWriter 50000 19.849 ms 0.3919 ms 0.0606 ms 2.62 500.0000 500.0000 500.0000 4.02 MB 0.25

@GabrielMarquezMatte
GabrielMarquezMatte merged commit 907cc5e into master Jul 21, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants