From 0f655050284695387b46a6eea553e2a17ea1839a Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Feb 2026 02:57:18 +0000 Subject: [PATCH 1/4] Add benchmark results to README Run BenchmarkDotNet across .NET 8.0, 9.0, and 10.0 and include the full results table showing performance and memory allocation for all mapping strategies and column access patterns. https://claude.ai/code/session_01Rjb1M2pVWrVUJJeyuL4VkN --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index 1b2c60f..897a1e5 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,55 @@ dotnet test dotnet run --project benchmarks/EnumerableDataReaderAdapter.Benchmarks -c Release ``` +### Results + +The benchmarks measure reading 10,000 rows through the `IDataReader` interface using different mapping strategies and column access patterns across .NET 8.0, 9.0, and 10.0. + +``` +BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat) +unknown 2.10GHz, 1 CPU, 16 logical and 16 physical cores +.NET SDK 10.0.103 + [Host] : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v4 + .NET 10.0 : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v4 + .NET 8.0 : .NET 8.0.24 (8.0.24, 8.0.2426.7010), X64 RyuJIT x86-64-v4 + .NET 9.0 : .NET 9.0.13 (9.0.13, 9.0.1326.6317), X64 RyuJIT x86-64-v4 +``` + +| Method | Job | Runtime | N | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +|----------------------------------------------- |---------- |---------- |------ |---------:|----------:|----------:|------:|--------:|-----:|--------:|----------:|------------:| +| DefaultMapping_ByColumnIndex | .NET 10.0 | .NET 10.0 | 10000 | 1.982 ms | 0.0376 ms | 0.0462 ms | 1.00 | 0.03 | 1 | 85.9375 | 351.82 KB | 1.00 | +| MappingExpressions_ByColumnIndex | .NET 10.0 | .NET 10.0 | 10000 | 1.902 ms | 0.0380 ms | 0.0842 ms | 0.96 | 0.05 | 1 | 85.9375 | 366.11 KB | 1.04 | +| MappingExpressions_ByColumnIndex_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 1.948 ms | 0.0363 ms | 0.0372 ms | 0.98 | 0.03 | 1 | 85.9375 | 351.88 KB | 1.00 | +| MappingDelegates_ByColumnIndex | .NET 10.0 | .NET 10.0 | 10000 | 1.968 ms | 0.0392 ms | 0.0524 ms | 0.99 | 0.03 | 1 | 85.9375 | 352.13 KB | 1.00 | +| MappingDelegates_ByColumnIndex_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 2.003 ms | 0.0396 ms | 0.0826 ms | 1.01 | 0.05 | 1 | 85.9375 | 351.88 KB | 1.00 | +| DefaultMapping_ByColumnName | .NET 10.0 | .NET 10.0 | 10000 | 2.034 ms | 0.0398 ms | 0.0458 ms | 1.03 | 0.03 | 1 | 85.9375 | 352.03 KB | 1.00 | +| MappingExpressions_ByColumnName | .NET 10.0 | .NET 10.0 | 10000 | 2.301 ms | 0.0457 ms | 0.0836 ms | 1.16 | 0.05 | 2 | 89.8438 | 366.32 KB | 1.04 | +| MappingExpressions_ByColumnName_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 2.041 ms | 0.0405 ms | 0.0845 ms | 1.03 | 0.05 | 1 | 85.9375 | 352.09 KB | 1.00 | +| MappingDelegates_ByColumnName | .NET 10.0 | .NET 10.0 | 10000 | 1.995 ms | 0.0390 ms | 0.0534 ms | 1.01 | 0.04 | 1 | 85.9375 | 352.34 KB | 1.00 | +| MappingDelegates_ByColumnName_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 2.032 ms | 0.0406 ms | 0.0655 ms | 1.03 | 0.04 | 1 | 85.9375 | 352.09 KB | 1.00 | +| | | | | | | | | | | | | | +| DefaultMapping_ByColumnIndex | .NET 8.0 | .NET 8.0 | 10000 | 2.119 ms | 0.0421 ms | 0.0517 ms | 1.00 | 0.03 | 1 | 85.9375 | 351.84 KB | 1.00 | +| MappingExpressions_ByColumnIndex | .NET 8.0 | .NET 8.0 | 10000 | 2.241 ms | 0.0423 ms | 0.0893 ms | 1.06 | 0.05 | 1 | 85.9375 | 366.11 KB | 1.04 | +| MappingExpressions_ByColumnIndex_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.102 ms | 0.0414 ms | 0.0736 ms | 0.99 | 0.04 | 1 | 85.9375 | 351.88 KB | 1.00 | +| MappingDelegates_ByColumnIndex | .NET 8.0 | .NET 8.0 | 10000 | 2.068 ms | 0.0378 ms | 0.0681 ms | 0.98 | 0.04 | 1 | 85.9375 | 352.42 KB | 1.00 | +| MappingDelegates_ByColumnIndex_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.109 ms | 0.0415 ms | 0.0608 ms | 1.00 | 0.04 | 1 | 85.9375 | 351.88 KB | 1.00 | +| DefaultMapping_ByColumnName | .NET 8.0 | .NET 8.0 | 10000 | 2.671 ms | 0.0533 ms | 0.0829 ms | 1.26 | 0.05 | 2 | 85.9375 | 352.05 KB | 1.00 | +| MappingExpressions_ByColumnName | .NET 8.0 | .NET 8.0 | 10000 | 2.808 ms | 0.0558 ms | 0.1272 ms | 1.33 | 0.07 | 2 | 89.8438 | 366.32 KB | 1.04 | +| MappingExpressions_ByColumnName_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.610 ms | 0.0512 ms | 0.0701 ms | 1.23 | 0.04 | 2 | 85.9375 | 352.09 KB | 1.00 | +| MappingDelegates_ByColumnName | .NET 8.0 | .NET 8.0 | 10000 | 2.486 ms | 0.0476 ms | 0.0962 ms | 1.17 | 0.05 | 2 | 85.9375 | 352.63 KB | 1.00 | +| MappingDelegates_ByColumnName_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.557 ms | 0.0504 ms | 0.0690 ms | 1.21 | 0.04 | 2 | 85.9375 | 352.09 KB | 1.00 | +| | | | | | | | | | | | | | +| DefaultMapping_ByColumnIndex | .NET 9.0 | .NET 9.0 | 10000 | 2.064 ms | 0.0391 ms | 0.0434 ms | 1.00 | 0.03 | 1 | 85.9375 | 351.82 KB | 1.00 | +| MappingExpressions_ByColumnIndex | .NET 9.0 | .NET 9.0 | 10000 | 2.272 ms | 0.0411 ms | 0.0831 ms | 1.10 | 0.05 | 2 | 85.9375 | 366.11 KB | 1.04 | +| MappingExpressions_ByColumnIndex_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.092 ms | 0.0404 ms | 0.0579 ms | 1.01 | 0.03 | 1 | 85.9375 | 351.88 KB | 1.00 | +| MappingDelegates_ByColumnIndex | .NET 9.0 | .NET 9.0 | 10000 | 2.056 ms | 0.0405 ms | 0.0643 ms | 1.00 | 0.04 | 1 | 85.9375 | 352.31 KB | 1.00 | +| MappingDelegates_ByColumnIndex_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.085 ms | 0.0409 ms | 0.0757 ms | 1.01 | 0.04 | 1 | 85.9375 | 351.88 KB | 1.00 | +| DefaultMapping_ByColumnName | .NET 9.0 | .NET 9.0 | 10000 | 2.601 ms | 0.0502 ms | 0.1080 ms | 1.26 | 0.06 | 3 | 85.9375 | 352.03 KB | 1.00 | +| MappingExpressions_ByColumnName | .NET 9.0 | .NET 9.0 | 10000 | 2.645 ms | 0.0515 ms | 0.0688 ms | 1.28 | 0.04 | 3 | 89.8438 | 366.32 KB | 1.04 | +| MappingExpressions_ByColumnName_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.547 ms | 0.0474 ms | 0.0752 ms | 1.23 | 0.04 | 3 | 85.9375 | 352.09 KB | 1.00 | +| MappingDelegates_ByColumnName | .NET 9.0 | .NET 9.0 | 10000 | 2.468 ms | 0.0490 ms | 0.0967 ms | 1.20 | 0.05 | 3 | 85.9375 | 352.52 KB | 1.00 | +| MappingDelegates_ByColumnName_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.481 ms | 0.0486 ms | 0.0982 ms | 1.20 | 0.05 | 3 | 85.9375 | 352.09 KB | 1.00 | + ## License [MIT](LICENSE) -- Copyright (c) 2018 Dimo Terziev From 7a4d9bed0a91e358c8d3b872e6dd6c596552be18 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Feb 2026 02:58:14 +0000 Subject: [PATCH 2/4] Add BenchmarkDotNet.Artifacts to .gitignore Prevent benchmark output artifacts from being tracked by git. https://claude.ai/code/session_01Rjb1M2pVWrVUJJeyuL4VkN --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 5ef2421..aa03cdf 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,9 @@ project.lock.json project.fragment.lock.json artifacts/ +# BenchmarkDotNet +BenchmarkDotNet.Artifacts/ + *_i.c *_p.c *_i.h From 4f18e6cfe243c4cc08f5ef666d4835e125a0be5c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 14 Feb 2026 09:46:17 +0000 Subject: [PATCH 3/4] Optimize performance with FrozenDictionary and remove .NET 8/9 from benchmarks - Replace Lazy with FrozenDictionary for column name lookups, providing optimized hashing and eliminating volatile reads on every access - Use FrozenDictionary.AlternateLookup> for allocation-free string lookups in GetOrdinal (NET9_0_OR_GREATER) - Fix IsDBNull double-evaluation bug that invoked the value getter twice - Use Span-based iteration in GetValues for improved bounds check elimination - Remove .NET 8.0 and .NET 9.0 from benchmark target runtimes - Update README with .NET 10-only benchmark results https://claude.ai/code/session_01Rjb1M2pVWrVUJJeyuL4VkN --- README.md | 52 ++++++------------- ...merableDataReaderAdapter.Benchmarks.csproj | 2 +- .../Program.cs | 2 - .../EnumerableExtensions.cs | 49 +++++++++++------ 4 files changed, 49 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 897a1e5..0dc716e 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ dotnet run --project benchmarks/EnumerableDataReaderAdapter.Benchmarks -c Releas ### Results -The benchmarks measure reading 10,000 rows through the `IDataReader` interface using different mapping strategies and column access patterns across .NET 8.0, 9.0, and 10.0. +The benchmarks measure reading 10,000 rows through the `IDataReader` interface using different mapping strategies and column access patterns on .NET 10.0. ``` BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.3 LTS (Noble Numbat) @@ -122,44 +122,22 @@ unknown 2.10GHz, 1 CPU, 16 logical and 16 physical cores .NET SDK 10.0.103 [Host] : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v4 .NET 10.0 : .NET 10.0.3 (10.0.3, 10.0.326.7603), X64 RyuJIT x86-64-v4 - .NET 8.0 : .NET 8.0.24 (8.0.24, 8.0.2426.7010), X64 RyuJIT x86-64-v4 - .NET 9.0 : .NET 9.0.13 (9.0.13, 9.0.1326.6317), X64 RyuJIT x86-64-v4 + +Job=.NET 10.0 Runtime=.NET 10.0 ``` -| Method | Job | Runtime | N | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | -|----------------------------------------------- |---------- |---------- |------ |---------:|----------:|----------:|------:|--------:|-----:|--------:|----------:|------------:| -| DefaultMapping_ByColumnIndex | .NET 10.0 | .NET 10.0 | 10000 | 1.982 ms | 0.0376 ms | 0.0462 ms | 1.00 | 0.03 | 1 | 85.9375 | 351.82 KB | 1.00 | -| MappingExpressions_ByColumnIndex | .NET 10.0 | .NET 10.0 | 10000 | 1.902 ms | 0.0380 ms | 0.0842 ms | 0.96 | 0.05 | 1 | 85.9375 | 366.11 KB | 1.04 | -| MappingExpressions_ByColumnIndex_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 1.948 ms | 0.0363 ms | 0.0372 ms | 0.98 | 0.03 | 1 | 85.9375 | 351.88 KB | 1.00 | -| MappingDelegates_ByColumnIndex | .NET 10.0 | .NET 10.0 | 10000 | 1.968 ms | 0.0392 ms | 0.0524 ms | 0.99 | 0.03 | 1 | 85.9375 | 352.13 KB | 1.00 | -| MappingDelegates_ByColumnIndex_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 2.003 ms | 0.0396 ms | 0.0826 ms | 1.01 | 0.05 | 1 | 85.9375 | 351.88 KB | 1.00 | -| DefaultMapping_ByColumnName | .NET 10.0 | .NET 10.0 | 10000 | 2.034 ms | 0.0398 ms | 0.0458 ms | 1.03 | 0.03 | 1 | 85.9375 | 352.03 KB | 1.00 | -| MappingExpressions_ByColumnName | .NET 10.0 | .NET 10.0 | 10000 | 2.301 ms | 0.0457 ms | 0.0836 ms | 1.16 | 0.05 | 2 | 89.8438 | 366.32 KB | 1.04 | -| MappingExpressions_ByColumnName_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 2.041 ms | 0.0405 ms | 0.0845 ms | 1.03 | 0.05 | 1 | 85.9375 | 352.09 KB | 1.00 | -| MappingDelegates_ByColumnName | .NET 10.0 | .NET 10.0 | 10000 | 1.995 ms | 0.0390 ms | 0.0534 ms | 1.01 | 0.04 | 1 | 85.9375 | 352.34 KB | 1.00 | -| MappingDelegates_ByColumnName_CachedMapping | .NET 10.0 | .NET 10.0 | 10000 | 2.032 ms | 0.0406 ms | 0.0655 ms | 1.03 | 0.04 | 1 | 85.9375 | 352.09 KB | 1.00 | -| | | | | | | | | | | | | | -| DefaultMapping_ByColumnIndex | .NET 8.0 | .NET 8.0 | 10000 | 2.119 ms | 0.0421 ms | 0.0517 ms | 1.00 | 0.03 | 1 | 85.9375 | 351.84 KB | 1.00 | -| MappingExpressions_ByColumnIndex | .NET 8.0 | .NET 8.0 | 10000 | 2.241 ms | 0.0423 ms | 0.0893 ms | 1.06 | 0.05 | 1 | 85.9375 | 366.11 KB | 1.04 | -| MappingExpressions_ByColumnIndex_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.102 ms | 0.0414 ms | 0.0736 ms | 0.99 | 0.04 | 1 | 85.9375 | 351.88 KB | 1.00 | -| MappingDelegates_ByColumnIndex | .NET 8.0 | .NET 8.0 | 10000 | 2.068 ms | 0.0378 ms | 0.0681 ms | 0.98 | 0.04 | 1 | 85.9375 | 352.42 KB | 1.00 | -| MappingDelegates_ByColumnIndex_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.109 ms | 0.0415 ms | 0.0608 ms | 1.00 | 0.04 | 1 | 85.9375 | 351.88 KB | 1.00 | -| DefaultMapping_ByColumnName | .NET 8.0 | .NET 8.0 | 10000 | 2.671 ms | 0.0533 ms | 0.0829 ms | 1.26 | 0.05 | 2 | 85.9375 | 352.05 KB | 1.00 | -| MappingExpressions_ByColumnName | .NET 8.0 | .NET 8.0 | 10000 | 2.808 ms | 0.0558 ms | 0.1272 ms | 1.33 | 0.07 | 2 | 89.8438 | 366.32 KB | 1.04 | -| MappingExpressions_ByColumnName_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.610 ms | 0.0512 ms | 0.0701 ms | 1.23 | 0.04 | 2 | 85.9375 | 352.09 KB | 1.00 | -| MappingDelegates_ByColumnName | .NET 8.0 | .NET 8.0 | 10000 | 2.486 ms | 0.0476 ms | 0.0962 ms | 1.17 | 0.05 | 2 | 85.9375 | 352.63 KB | 1.00 | -| MappingDelegates_ByColumnName_CachedMapping | .NET 8.0 | .NET 8.0 | 10000 | 2.557 ms | 0.0504 ms | 0.0690 ms | 1.21 | 0.04 | 2 | 85.9375 | 352.09 KB | 1.00 | -| | | | | | | | | | | | | | -| DefaultMapping_ByColumnIndex | .NET 9.0 | .NET 9.0 | 10000 | 2.064 ms | 0.0391 ms | 0.0434 ms | 1.00 | 0.03 | 1 | 85.9375 | 351.82 KB | 1.00 | -| MappingExpressions_ByColumnIndex | .NET 9.0 | .NET 9.0 | 10000 | 2.272 ms | 0.0411 ms | 0.0831 ms | 1.10 | 0.05 | 2 | 85.9375 | 366.11 KB | 1.04 | -| MappingExpressions_ByColumnIndex_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.092 ms | 0.0404 ms | 0.0579 ms | 1.01 | 0.03 | 1 | 85.9375 | 351.88 KB | 1.00 | -| MappingDelegates_ByColumnIndex | .NET 9.0 | .NET 9.0 | 10000 | 2.056 ms | 0.0405 ms | 0.0643 ms | 1.00 | 0.04 | 1 | 85.9375 | 352.31 KB | 1.00 | -| MappingDelegates_ByColumnIndex_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.085 ms | 0.0409 ms | 0.0757 ms | 1.01 | 0.04 | 1 | 85.9375 | 351.88 KB | 1.00 | -| DefaultMapping_ByColumnName | .NET 9.0 | .NET 9.0 | 10000 | 2.601 ms | 0.0502 ms | 0.1080 ms | 1.26 | 0.06 | 3 | 85.9375 | 352.03 KB | 1.00 | -| MappingExpressions_ByColumnName | .NET 9.0 | .NET 9.0 | 10000 | 2.645 ms | 0.0515 ms | 0.0688 ms | 1.28 | 0.04 | 3 | 89.8438 | 366.32 KB | 1.04 | -| MappingExpressions_ByColumnName_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.547 ms | 0.0474 ms | 0.0752 ms | 1.23 | 0.04 | 3 | 85.9375 | 352.09 KB | 1.00 | -| MappingDelegates_ByColumnName | .NET 9.0 | .NET 9.0 | 10000 | 2.468 ms | 0.0490 ms | 0.0967 ms | 1.20 | 0.05 | 3 | 85.9375 | 352.52 KB | 1.00 | -| MappingDelegates_ByColumnName_CachedMapping | .NET 9.0 | .NET 9.0 | 10000 | 2.481 ms | 0.0486 ms | 0.0982 ms | 1.20 | 0.05 | 3 | 85.9375 | 352.09 KB | 1.00 | +| Method | N | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio | +|----------------------------------------------- |------ |---------:|----------:|----------:|------:|--------:|-----:|--------:|----------:|------------:| +| DefaultMapping_ByColumnIndex | 10000 | 1.977 ms | 0.0393 ms | 0.0623 ms | 1.00 | 0.04 | 1 | 85.9375 | 352.23 KB | 1.00 | +| MappingExpressions_ByColumnIndex | 10000 | 2.047 ms | 0.0406 ms | 0.0732 ms | 1.04 | 0.05 | 1 | 89.8438 | 366.67 KB | 1.04 | +| MappingExpressions_ByColumnIndex_CachedMapping | 10000 | 1.982 ms | 0.0385 ms | 0.0564 ms | 1.00 | 0.04 | 1 | 85.9375 | 352.29 KB | 1.00 | +| MappingDelegates_ByColumnIndex | 10000 | 1.970 ms | 0.0392 ms | 0.0575 ms | 1.00 | 0.04 | 1 | 85.9375 | 352.53 KB | 1.00 | +| MappingDelegates_ByColumnIndex_CachedMapping | 10000 | 1.969 ms | 0.0391 ms | 0.0841 ms | 1.00 | 0.05 | 1 | 85.9375 | 352.29 KB | 1.00 | +| DefaultMapping_ByColumnName | 10000 | 2.053 ms | 0.0408 ms | 0.0705 ms | 1.04 | 0.05 | 1 | 85.9375 | 352.23 KB | 1.00 | +| MappingExpressions_ByColumnName | 10000 | 2.268 ms | 0.0450 ms | 0.0776 ms | 1.15 | 0.05 | 2 | 89.8438 | 366.6 KB | 1.04 | +| MappingExpressions_ByColumnName_CachedMapping | 10000 | 2.006 ms | 0.0385 ms | 0.0804 ms | 1.02 | 0.05 | 1 | 85.9375 | 352.29 KB | 1.00 | +| MappingDelegates_ByColumnName | 10000 | 2.016 ms | 0.0397 ms | 0.0441 ms | 1.02 | 0.04 | 1 | 85.9375 | 352.53 KB | 1.00 | +| MappingDelegates_ByColumnName_CachedMapping | 10000 | 2.024 ms | 0.0389 ms | 0.0519 ms | 1.02 | 0.04 | 1 | 85.9375 | 352.37 KB | 1.00 | ## License diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj index 3150f11..c144ddb 100644 --- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj +++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net9.0;net10.0 + net10.0 diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs index 67f3b81..5a7bf22 100644 --- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs +++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs @@ -23,8 +23,6 @@ public DataStructure( } - [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net80)] - [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net90)] [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net10_0)] [RPlotExporter, RankColumn] [MemoryDiagnoser] diff --git a/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs b/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs index 47cbd13..dcf4e6d 100644 --- a/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs +++ b/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Frozen; using System.Data; using System.Data.Common; using System.Runtime.CompilerServices; @@ -39,7 +40,10 @@ private sealed class EnumerableReaderAdapter : DbDataReader private bool _isClosed = false; private IEnumerator _enumerator; private T _current = default!; - private readonly Lazy> _columnLookup; + private readonly FrozenDictionary _columnLookup; +#if NET9_0_OR_GREATER + private readonly FrozenDictionary.AlternateLookup> _alternateLookup; +#endif private long _rowCount = 0; public EnumerableReaderAdapter( @@ -48,15 +52,16 @@ public EnumerableReaderAdapter( { _enumerator = rows.GetEnumerator(); _mappings = mappings; - _columnLookup = new Lazy>(() => + + var dict = new Dictionary(mappings.Length); + for (int i = 0; i < mappings.Length; i++) { - var result = new Dictionary(_mappings.Length); - for (int i = 0; i < _mappings.Length; i++) - { - result.Add(_mappings[i].ColumnName, i); - } - return result; - }); + dict.Add(mappings[i].ColumnName, i); + } + _columnLookup = dict.ToFrozenDictionary(); +#if NET9_0_OR_GREATER + _alternateLookup = _columnLookup.GetAlternateLookup>(); +#endif } public override bool HasRows => true; @@ -125,20 +130,25 @@ public override void Close() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int GetOrdinal(string name) => _columnLookup.Value[name]; +#if NET9_0_OR_GREATER + public override int GetOrdinal(string name) => _alternateLookup[name.AsSpan()]; +#else + public override int GetOrdinal(string name) => _columnLookup[name]; +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public override object GetValue(int i) => _mappings[i].ValueGetter(_current)!; public override int GetValues(object?[] values) { - var max = values.Length < _mappings.Length - ? values.Length - : _mappings.Length; + var max = Math.Min(values.Length, _mappings.Length); - for (int i = 0; i < max; i++) + ReadOnlySpan<(string ColumnName, Type ColumnType, Func ValueGetter)> mappingSpan = _mappings.AsSpan(0, max); + Span valuesSpan = values.AsSpan(0, max); + + for (int i = 0; i < mappingSpan.Length; i++) { - values[i] = _mappings[i].ValueGetter(_current); + valuesSpan[i] = mappingSpan[i].ValueGetter(_current); } return max; @@ -167,7 +177,14 @@ public override int GetValues(object?[] values) public override string GetString(int i) => (string)GetValue(i); public override decimal GetDecimal(int i) => (decimal)GetValue(i); public override DateTime GetDateTime(int i) => (DateTime)GetValue(i); - public override bool IsDBNull(int i) => GetValue(i) == null || GetValue(i) == DBNull.Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override bool IsDBNull(int i) + { + var value = GetValue(i); + return value is null or DBNull; + } + public override int FieldCount => _mappings.Length; } } From 0e32dbceb53aa22cf93d7cfa958e4d1f40d33562 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 15 Feb 2026 20:45:06 +0000 Subject: [PATCH 4/4] Drop .NET 8 support, clean up #if directives, restore .NET 9 benchmark - Remove net8.0 target from library and test projects (minimum is now net9.0) - Remove all #if NET9_0_OR_GREATER guards since net9.0 is the floor; FrozenDictionary and AlternateLookup are now used unconditionally - Restore .NET 9 runtime in the benchmark project alongside .NET 10 - Update README to reflect supported frameworks https://claude.ai/code/session_01Rjb1M2pVWrVUJJeyuL4VkN --- README.md | 2 +- .../EnumerableDataReaderAdapter.Benchmarks.csproj | 2 +- .../EnumerableDataReaderAdapter.Benchmarks/Program.cs | 1 + .../EnumerableDataReaderAdapter.csproj | 2 +- src/EnumerableDataReaderAdapter/EnumerableExtensions.cs | 8 -------- .../EnumerableDataReaderAdapter.Tests.csproj | 2 +- 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0dc716e..5e99683 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A lightweight .NET library that converts `IEnumerable` into an `IDataReader`, - **Automatic property mapping** -- public properties are discovered automatically when no explicit mapping is provided. - **Fluent column mapping API** -- choose exactly which columns to expose using expression-based or delegate-based mappings. - **Computed columns** -- map constant values or derived expressions that don't correspond to a property. -- **Multi-target** -- supports .NET 8.0, .NET 9.0, and .NET 10.0. +- **Multi-target** -- supports .NET 9.0 and .NET 10.0. ## Installation diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj index c144ddb..169efc4 100644 --- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj +++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/EnumerableDataReaderAdapter.Benchmarks.csproj @@ -2,7 +2,7 @@ Exe - net10.0 + net9.0;net10.0 diff --git a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs index 5a7bf22..aef5891 100644 --- a/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs +++ b/benchmarks/EnumerableDataReaderAdapter.Benchmarks/Program.cs @@ -23,6 +23,7 @@ public DataStructure( } + [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net90)] [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net10_0)] [RPlotExporter, RankColumn] [MemoryDiagnoser] diff --git a/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj b/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj index d2aec16..fda662e 100644 --- a/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj +++ b/src/EnumerableDataReaderAdapter/EnumerableDataReaderAdapter.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 diff --git a/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs b/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs index dcf4e6d..afdd04e 100644 --- a/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs +++ b/src/EnumerableDataReaderAdapter/EnumerableExtensions.cs @@ -41,9 +41,7 @@ private sealed class EnumerableReaderAdapter : DbDataReader private IEnumerator _enumerator; private T _current = default!; private readonly FrozenDictionary _columnLookup; -#if NET9_0_OR_GREATER private readonly FrozenDictionary.AlternateLookup> _alternateLookup; -#endif private long _rowCount = 0; public EnumerableReaderAdapter( @@ -59,9 +57,7 @@ public EnumerableReaderAdapter( dict.Add(mappings[i].ColumnName, i); } _columnLookup = dict.ToFrozenDictionary(); -#if NET9_0_OR_GREATER _alternateLookup = _columnLookup.GetAlternateLookup>(); -#endif } public override bool HasRows => true; @@ -130,11 +126,7 @@ public override void Close() } [MethodImpl(MethodImplOptions.AggressiveInlining)] -#if NET9_0_OR_GREATER public override int GetOrdinal(string name) => _alternateLookup[name.AsSpan()]; -#else - public override int GetOrdinal(string name) => _columnLookup[name]; -#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] public override object GetValue(int i) => _mappings[i].ValueGetter(_current)!; diff --git a/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj b/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj index ac0cff8..a285127 100644 --- a/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj +++ b/test/EnumerableDataReaderAdapter.Tests/EnumerableDataReaderAdapter.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0 + net9.0;net10.0 false