diff --git a/Excel_PRIME.RangeBench/Excel_PRIME.RangeBench.csproj b/Excel_PRIME.RangeBench/Excel_PRIME.RangeBench.csproj index 3fb621d..2ea0cd4 100644 --- a/Excel_PRIME.RangeBench/Excel_PRIME.RangeBench.csproj +++ b/Excel_PRIME.RangeBench/Excel_PRIME.RangeBench.csproj @@ -20,7 +20,7 @@ - + diff --git a/Excel_PRIME.Tests/Excel_PRIME.Tests.csproj b/Excel_PRIME.Tests/Excel_PRIME.Tests.csproj index 6757b52..19dc3c1 100644 --- a/Excel_PRIME.Tests/Excel_PRIME.Tests.csproj +++ b/Excel_PRIME.Tests/Excel_PRIME.Tests.csproj @@ -51,7 +51,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + true diff --git a/Excel_PRIME.Tests/SheetTests.cs b/Excel_PRIME.Tests/SheetTests.cs index 38b5662..f613e41 100644 --- a/Excel_PRIME.Tests/SheetTests.cs +++ b/Excel_PRIME.Tests/SheetTests.cs @@ -25,7 +25,7 @@ public async Task A010_StepThroughEmptyXlsx(string fileName, int expected) } [Test] - [TestCase("Data/multisheet1.xlsx", new[] { "one", "two", "three", "b", "a" })] + [TestCase("Data/multisheet1.xlsx", new[] { "one", "two", "three", "b", "a", "a (2)", "a (3)", "b (2)", "one (3)", "two (3)", "three (3)", "b (3)" })] [TestCase("Data/singlesheet.xlsx", new[] { "one" })] [TestCase("Data/sample_file_bad.xlsx", new[] { "MasterInvoice_Detailed_XLSX" })] // This file contains package relations that use absolute rooted paths instead of relative paths. "/xl/workbook.xml" vs "xl/workbook.xml" [TestCase("Data/sample_file_good.xlsx", new[] { "MasterInvoice_Detailed_XLSX" })] diff --git a/Excel_PRIME/Excel_PRIME.csproj b/Excel_PRIME/Excel_PRIME.csproj index 4f882a7..7573307 100644 --- a/Excel_PRIME/Excel_PRIME.csproj +++ b/Excel_PRIME/Excel_PRIME.csproj @@ -25,7 +25,7 @@ - + diff --git a/Excel_PRIME/Implementation/XmlReaderHelpers.cs b/Excel_PRIME/Implementation/XmlReaderHelpers.cs index 32df16d..7188857 100644 --- a/Excel_PRIME/Implementation/XmlReaderHelpers.cs +++ b/Excel_PRIME/Implementation/XmlReaderHelpers.cs @@ -94,8 +94,6 @@ public Task CreateSheetReaderAsync(NonClosingStream st return Task.FromResult(reader); } - public string GetSheetFileName(int offsetSheetId) => $"xl/worksheets/sheet{offsetSheetId}.xml"; - /// public IOpenXmlSheetReader CreateSheetReader(NonClosingStream stream, InstanceContext instanceContext, XmlNameTable sharedNameTable, CancellationToken ct) diff --git a/Excel_PRIME/Implementation/XmlWorkBookReader.cs b/Excel_PRIME/Implementation/XmlWorkBookReader.cs index b105829..216ac47 100644 --- a/Excel_PRIME/Implementation/XmlWorkBookReader.cs +++ b/Excel_PRIME/Implementation/XmlWorkBookReader.cs @@ -44,7 +44,7 @@ protected void OpenWorkbookStream() => public IEnumerable> GetSheetNames([EnumeratorCancellation] CancellationToken ct) { - IReadOnlyDictionary worksheetRels = PopulateWorkSheetRels(ct); + Dictionary worksheetRels = PopulateWorkSheetRels(ct); string sheetsRefAtom = _readerWb.NameTable.Add("sheets"); if (!worksheetRels.Any() @@ -78,7 +78,7 @@ public IEnumerable> GetSheetNames([EnumeratorCancel } } - private IReadOnlyDictionary PopulateWorkSheetRels(CancellationToken ct) + private Dictionary PopulateWorkSheetRels(CancellationToken ct) { using Stream? streamRelWb = _zipReader.GetEntry("xl/_rels/workbook.xml.rels"); using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings @@ -251,7 +251,7 @@ public XmlWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) public async IAsyncEnumerable> GetSheetNamesAsync([EnumeratorCancellation] CancellationToken ct) { - IReadOnlyDictionary worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false); + Dictionary worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false); string sheetsRefAtom = _readerWb.NameTable.Add("sheets"); if (!worksheetRels.Any() @@ -284,7 +284,7 @@ public async IAsyncEnumerable> GetSheetNamesAsync([ } } - private async Task> PopulateWorkSheetRelsAsync(CancellationToken ct) + private async Task> PopulateWorkSheetRelsAsync(CancellationToken ct) { using Stream? streamRelWb = await _zipReaderA.GetEntryAsync("xl/_rels/workbook.xml.rels", ct).ConfigureAwait(false); using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings diff --git a/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs b/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs index 1852f75..63c1cee 100644 --- a/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs +++ b/Excel_PRIME/XlsbImp/XlsbReaderHelpers.cs @@ -99,8 +99,6 @@ public Task CreateSheetReaderAsync(NonClosingStream st return Task.FromResult(reader); } - public string GetSheetFileName(int offsetSheetId) => $"xl/worksheets/sheet{offsetSheetId}.bin"; - /// public IOpenXmlSheetReader CreateSheetReader(NonClosingStream stream, InstanceContext instanceContext, XmlNameTable _, CancellationToken ct) diff --git a/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs b/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs index 747aff4..4ad12b5 100644 --- a/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs +++ b/Excel_PRIME/XlsbImp/XlsbWorkBookReader.cs @@ -37,7 +37,7 @@ protected void OpenWorkbookStream() => public IEnumerable> GetSheetNames([EnumeratorCancellation] CancellationToken ct) { - IReadOnlyDictionary worksheetRels = PopulateWorkSheetRels(ct); + Dictionary worksheetRels = PopulateWorkSheetRels(ct); if (!worksheetRels.Any()) { yield break; @@ -75,7 +75,7 @@ public IEnumerable> GetSheetNames([EnumeratorCancel nextRecord.Dispose(); } - private IReadOnlyDictionary PopulateWorkSheetRels(CancellationToken ct) + private Dictionary PopulateWorkSheetRels(CancellationToken ct) { using Stream? streamRelWb = _zipReader.GetEntry("xl/_rels/workbook.bin.rels"); using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings @@ -305,7 +305,7 @@ public XlsbWorkBookReaderAsync(IZipReaderAsync zipReader, CancellationToken ct) public async IAsyncEnumerable> GetSheetNamesAsync([EnumeratorCancellation] CancellationToken ct) { - IReadOnlyDictionary worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false); + Dictionary worksheetRels = await PopulateWorkSheetRelsAsync(ct).ConfigureAwait(false); if (!worksheetRels.Any()) { yield break; @@ -343,7 +343,7 @@ public async IAsyncEnumerable> GetSheetNamesAsync([ nextRecord.Dispose(); } - private async Task> PopulateWorkSheetRelsAsync(CancellationToken ct) + private async Task> PopulateWorkSheetRelsAsync(CancellationToken ct) { using Stream? streamRelWb = await _zipReaderA.GetEntryAsync("xl/_rels/workbook.bin.rels", ct).ConfigureAwait(false); using XmlReader readerRels = XmlReader.Create(streamRelWb, new XmlReaderSettings diff --git a/Excel_PRIMEXlsb.Bench/Excel_PRIMEXlsb.Bench.csproj b/Excel_PRIMEXlsb.Bench/Excel_PRIMEXlsb.Bench.csproj index 41d5e4d..a8f00cf 100644 --- a/Excel_PRIMEXlsb.Bench/Excel_PRIMEXlsb.Bench.csproj +++ b/Excel_PRIMEXlsb.Bench/Excel_PRIMEXlsb.Bench.csproj @@ -14,7 +14,7 @@ - + diff --git a/Excel_PRIMEXlsb.RangeBench/Excel_PRIMEXlsb.RangeBench.csproj b/Excel_PRIMEXlsb.RangeBench/Excel_PRIMEXlsb.RangeBench.csproj index f99599f..9d4c227 100644 --- a/Excel_PRIMEXlsb.RangeBench/Excel_PRIMEXlsb.RangeBench.csproj +++ b/Excel_PRIMEXlsb.RangeBench/Excel_PRIMEXlsb.RangeBench.csproj @@ -14,7 +14,7 @@ - + diff --git a/Performance.md b/Performance.md index 25cfcff..dcacb65 100644 --- a/Performance.md +++ b/Performance.md @@ -871,7 +871,7 @@ And then slightly different versions of the following dependent on date: ``` ----- -# 2026-01-16 +# 2026-01-16 - V3 - Remove some `AggressiveOptimization` and allow `i-cache` to do its job - Implement "Hot-Paths" for cell type access - Reduce some memory allocations for ReadOnly CellCollections @@ -903,3 +903,33 @@ And then slightly different versions of the following dependent on date: | ParallelEveryCellAsyncExcel_PrimeTwice | sampl(...).xlsx [30] | 2.10x slower | 303000.0000 | 1000.0000 | - | 2420.25 MB | 8.23x more | ``` +# 2026-01-18 - V4 Alpha +- Internal implementation of `IOpenXmlWorkBookReader::GetSheetNames` now returns the relative path to the sheetName +``` +| Method | FileName | Ratio | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio | +|------------------------ |--------------------- |-------------:|------------:|-----------:|-----------:|-----------:|------------:| +| Sylvan | 100mb.xlsx | baseline | 43000.0000 | 41000.0000 | 5000.0000 | 338.72 MB | | +| XlsxHelper | 100mb.xlsx | 4.24x slower | 424000.0000 | 5000.0000 | 2000.0000 | 3380.59 MB | 9.98x more | +| AsyncExcel_Prime | 100mb.xlsx | 1.39x slower | 259000.0000 | 43000.0000 | 6000.0000 | 2029.28 MB | 5.99x more | +| Excel_Prime | 100mb.xlsx | 1.19x slower | 162000.0000 | 47000.0000 | 6000.0000 | 1256.38 MB | 3.71x more | +| PrlAsyncExcel_PrimeTwice| 100mb.xlsx | 2.27x slower | 440000.0000 | 50000.0000 | 6000.0000 | 3464.3 MB | 10.23x more | +| | | | | | | | | +| Sylvan | Blank(...).xlsx [30] | baseline | 40000.0000 | 2000.0000 | 1000.0000 | 318.9 MB | | +| XlsxHelper | Blank(...).xlsx [30] | 1.01x slower | 218000.0000 | 1000.0000 | - | 1739.24 MB | 5.45x more | +| AsyncExcel_Prime | Blank(...).xlsx [30] | 1.08x slower | 347000.0000 | 42000.0000 | 41000.0000 | 2566.97 MB | 8.05x more | +| Excel_Prime | Blank(...).xlsx [30] | 1.11x faster | 195000.0000 | 42000.0000 | 41000.0000 | 1361.65 MB | 4.27x more | +| PrlAsyncExcel_PrimeTwice| Blank(...).xlsx [30] | 2.40x slower | 693000.0000 | 85000.0000 | 83000.0000 | 5155.95 MB | 16.17x more | +| | | | | | | | | +| Sylvan | sampl(...).xlsx [34] | baseline | 33000.0000 | - | - | 265.67 MB | | +| XlsxHelper | sampl(...).xlsx [34] | 1.12x faster | 100000.0000 | - | - | 799.73 MB | 3.01x more | +| AsyncExcel_Prime | sampl(...).xlsx [34] | 1.04x faster | 148000.0000 | 1000.0000 | - | 1184.05 MB | 4.46x more | +| Excel_Prime | sampl(...).xlsx [34] | 1.25x faster | 79000.0000 | 1000.0000 | - | 632.38 MB | 2.38x more | +| PrlAsyncExcel_PrimeTwice| sampl(...).xlsx [34] | 2.05x slower | 297000.0000 | 1000.0000 | - | 2376.06 MB | 8.94x more | +| | | | | | | | | +| Sylvan | sampl(...).xlsx [30] | baseline | 36000.0000 | - | - | 294.16 MB | | +| XlsxHelper | sampl(...).xlsx [30] | 1.09x faster | 93000.0000 | - | - | 742.13 MB | 2.52x more | +| AsyncExcel_Prime | sampl(...).xlsx [30] | 1.03x slower | 151000.0000 | 1000.0000 | - | 1207.31 MB | 4.10x more | +| Excel_Prime | sampl(...).xlsx [30] | 1.22x faster | 82000.0000 | 1000.0000 | - | 656.03 MB | 2.23x more | +| PrlAsyncExcel_PrimeTwice| sampl(...).xlsx [30] | 2.17x slower | 303000.0000 | - | - | 2420.4 MB | 8.23x more | +``` +----- diff --git a/README.md b/README.md index 71025ce..789fd34 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,23 @@ Lets take each of the above elements and explain: Flag Counter ----- + +- [Targets ๐ŸŽฏ](#targets-) + - [Phase 0](#phase-0) + - [Phase Alpha](#phase-alpha) + - [Phase Beta - Benchmarks โฑ๏ธ](#phase-beta-benchmarks-) + - [Phase 1 - MVP ๐Ÿ”](#phase-1-mvp-) + - [Phase 2 - RC](#phase-2-rc) + - [V2 Changes](#v2-changes) + - [2025-12-14](#2025-12-14) + - [Phase V3 - XLS**B** ๐Ÿ’พ (BIFF12)](#phase-v3-xlsb-biff12) +- [V3 Changes](#v3-changes) +- [2026-01-16](#2026-01-16) + - [Phase V4 - Specific Cell value type(s) #๏ธโƒฃ - Alpha](#phase-v4-specific-cell-value-types-alpha) + - [Phase 5 - Third Party Nugets ๐Ÿ“ฆ](#phase-5-third-party-nugets-) + - [Phase 6 - ideas ๐Ÿ’ก](#phase-6-ideas-) + +----- # Targets ๐ŸŽฏ ## Phase 0 @@ -137,37 +154,16 @@ Lets take each of the above elements and explain: ## Phase 1 - MVP ๐Ÿ” - โœ… Add `IEnumerable`s and benchmark - - โš ๏ธ Still not convinced whether to implement "all the way down" - โœ… Implement `XmlReader.Create` for - - โœ… Loading sharedStrings - - โœ… Sheet loading - - โœ… Some Profiling Enahancements - - โœ… Performance [2025-10-18-pm](Performance.md#2025-10-18-pm) - โœ… More Benchmarks - Now With `FastExcel` - โœ… Some Profiling Enahancements - - ๐Ÿš€ Big Performance improvements [2025-10-19-pm](Performance.md#2025-10-19-pm) - โœ… Better `Storage` of the SharedStrings - - โœ… Use of LazyLoading Class - - โš ๏ธ Performance [2025-10-14](Performance.md#2025-10-14) - - โœ… Use of Derived `XmlNamedTable` implementations - - โœ… Locking for separate sheet thread reading - - โš ๏ธ Performance [2025-10-25](Performance.md##2025-10-25) - - โœ… Restricted storage (i.e. do not return things that are not relevant) - - ๐Ÿš€ Big Performance improvements [2025-10-26](Performance.md#2025-10-26) - โœ… Cell object type ๐Ÿ“… - - ๐Ÿš€ Big Performance improvements [2025-11-01](Performance.md#2025-11-01) - - โœ… Cell converted when read (i.e. you will know the type that you want, and you can convert it.) - - ๐Ÿš€ Big Performance improvements [2025-11-04](Performance.md#2025-11-04) - โœ… Use internal `ZipEntry` rented buffer - - โœ… Add and explain usage in options - - ๐Ÿš€ Big Performance improvements [2025-11-07](Performance.md#2025-11-07) - โœ… Investigation into the smallest function ๐Ÿ’ช - - ๐Ÿš€ More Performance improvements [2025-11-08](Performance.md#2025-11-08) - โœ… Optimise for `CellConversion.None` ๐Ÿ’ช - - ๐Ÿš€ More Performance improvements [2025-11-12](Performance.md#2025-11-12) - โœ… Parallel Sheet threads Access - - โœ… Multiple times (with locking) - โœ… Nuget - โœ… Beta etc. - ๐ŸŽŠ Released as Nuget V1.yyMM.dd -> **`1.2511.14`** @@ -175,54 +171,35 @@ Lets take each of the above elements and explain: ## Phase 2 - RC - โœ… Add `IEnumerable`s _All_ the way down โคต๏ธ - - i.e. remove the need for Asynchronous awaits - - ๐Ÿš€ Yielding More Performance improvements [2025-11-19](Performance.md#2025-11-19) - - โ›“๏ธโ€๐Ÿ’ฅ **Breaking Change** ๐Ÿ”ฉ - - The Async classes now have `Async` appended to be distinct from the non async versions - - But, `Async` inherit from the non, so they are interchangable - โœ… Nuget - โœ… Manual workflow deploy Release - โœ… Manual workflow deploy Beta - โœ… Read `definedName`s (Ranges / Cell / Value / Dynamic) ๐Ÿ“‡ - - โœ… Read from global - - โœ… Handle Dynamics (i.e. do not fall over! ๐Ÿคท) - โœ… Deal with blank rows in a sheet ๐Ÿ—‹ - - โœ… Return a `null` cell row - โœ… Deal with Empty cells in a row ๐Ÿ—… - - โœ… Return a `null` cell (e.g. ``) - โœ… Implement Sheet scoping of `definedName`s - - โœ… i.e. `'Try it Yourself'!$C$12:$E$12` - - Note: The above will be referenced as `OrderSize (Try it Yourself)` as shown in LibreOffice. - โœ… Implement Row extraction ๐Ÿ“Ÿ - - โœ… Allow ColumnHeader addressing (i.e. start -> end columns) - โœ… Implement RangeExtraction ๐Ÿ“ฒ - - โœ… Global rangeNames - - โœ… Make `DefinedName`'s work with `localSheetId`definitions - - โœ… User defined, using the `"A1:B10"` or `"$A$1:$B$10"` syntax - - [Range Performance on 2025-12-10](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-10) - โœ… Add Benchmarks for "Excel readers" That perform Range Extraction - โœ… `ClosedXML` Version="0.105.0" - - [Performance on 2025-11-25](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-25) - โœ… `EPPlus_LPGL` Version="4.5.3.13" - - [Performance on 2025-11-25](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-25) - โš ๏ธ `FastExcel` Version="3.0.13" -> [Fails on Range Extraction](https://github.com/ahmedwalid05/FastExcel/issues/89) - โœ… `FreeSpire.XLS` Version="14.2.0" - - [Performance on 2025-11-27](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-27) - โœ… `Aspose.Cells` Version="25.11.0" - - [Performance on 2025-11-28](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-28) - โš ๏ธ Extend bencmarks to cover the other large file types - It appears that most of the others do not like the `pivot-tables` file.!! ๐Ÿคฏ + - [Performance on 2025-11-28](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-11-28) - โœ… Investigate _memory usage_(s) ๐Ÿง‘โ€๐Ÿ’ป - - โœ… Some performance improvements ๐Ÿƒโ€โžก๏ธ [Performance on 2025-12-01](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-01) - - โœ… More performance improvements ๐Ÿƒโ€โžก๏ธ [Performance on 2025-12-04](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-04) - โœ… Sacrificed a little speed โžก๏ธ [Performance on 2025-12-07](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-07) - โœ… Release as Nuget V2.2512-10 ๐Ÿ’จ ----- -## V2 Changes +## V2 Changes โžก๏ธ ### 2025-12-14 - Implement [GetUserRange(...)](https://github.com/Smurf-IV/Excel_PRIME/issues/7) - [Range Performance on 2025-12-14](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-14) +----- + ## Phase V3 - XLS**B** ๐Ÿ’พ (BIFF12) - โ›“๏ธโ€๐Ÿ’ฅ **Breaking Change(s)** - `FileType` has been removed, and Open via the Public class type @@ -260,6 +237,14 @@ Lets take each of the above elements and explain: - ๐Ÿš€ Big Performance improvements [2026-01-11](Performance.md#2026-01-11) - ๐ŸŽŠ Released **V3** as Nuget `V3.2601.11` ----- +# V3 Changes โžก๏ธ +# 2026-01-16 +- Remove some `AggressiveOptimization` and allow `i-cache` to do its job +- Implement "Hot-Paths" for cell type access +- Reduce some memory allocations for ReadOnly CellCollections + - [2026-01-16](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2026-01-16) + +----- ## Phase V4 - Specific Cell value type(s) #๏ธโƒฃ - Alpha - โ›“๏ธโ€๐Ÿ’ฅ **Breaking Change(s)** diff --git a/Release_Notes.md b/Release_Notes.md index 518a8c6..4fc0181 100644 --- a/Release_Notes.md +++ b/Release_Notes.md @@ -6,7 +6,8 @@ - Internal Creation of WorkBooks - Internal implementation of `IOpenXmlWorkBookReader::GetSheetNames` now returns the relative path to the sheetName -๏ปฟ# 2026-01-16 - V3 + +# 2026-01-16 - V3 - Remove some `AggressiveOptimization` and allow `i-cache` to do its job - Implement "Hot-Paths" for cell type access - Reduce some memory allocations for ReadOnly CellCollections @@ -22,21 +23,17 @@ - Removal of the Conversion options `Number###` - Changed `GetAllCells` to return `IReadOnlyList?` - Watch out for those null rows ! -- ๐Ÿš€ Big Performance improvements [2026-01-11](Performance.md#2026-01-11) +- ๐Ÿš€ Big Performance improvements [2026-01-11](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2026-01-11) ๏ปฟ # 2025-12-14 - V2 +- โ›“๏ธโ€๐Ÿ’ฅ **Breaking Change** ๐Ÿ”ฉ + - The Async classes now have `Async` appended to be distinct from the non async versions + - But, `Async` inherit from the non, so they are interchangable - Implement [GetUserRange(...)](https://github.com/Smurf-IV/Excel_PRIME/issues/7) - [Range Performance on 2025-12-14](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-14) - -# 2025-12-10 - V2 RC - User defined, using the `"A1:B10"` or `"$A$1:$B$10"` syntax - [Range Performance on 2025-12-10](https://github.com/Smurf-IV/Excel_PRIME/blob/main/Performance.md#2025-12-10) - -# 2025-1#-## - Beta V2 - Improve _memory usage_(s) ๐Ÿง‘โ€๐Ÿ’ป -- โ›“๏ธโ€๐Ÿ’ฅ **Breaking Change** ๐Ÿ”ฉ - - The Async classes now have `Async` appended to be distinct from the non async versions - - But, `Async` inherit from the non, so they are interchangable - Make `DefinedName`'s work with `localSheetId`definitions - Benchmarks for range extraction - Add `IEnumerable`s _All_ the way down โคต๏ธ