From 7934185dcda9d072b1a90547ac90093216908af8 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Tue, 10 Nov 2026 23:16:05 +0100 Subject: [PATCH 1/2] Updated source and tests to target .NET 11 --- .github/workflows/codeql-analysis.yml | 8 ++++---- .github/workflows/dotnet.yml | 4 ++++ src/MiniExcel/MiniExcelLibs.csproj | 6 +++--- tests/MiniExcelTests/MiniExcelTests.csproj | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e88bed24..8a2902e4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,15 +40,15 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup .NET 8 + - name: Setup .NET 10 uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 10.0.x - - name: Setup .NET 10 + - name: Setup .NET 11 uses: actions/setup-dotnet@v4 with: - dotnet-version: 10.0.x + dotnet-version: 11.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 1c404fb6..97131f4b 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -25,6 +25,10 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x + - name: Setup .NET 11 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 11.0.x - name: Restore dependencies run: dotnet restore env: diff --git a/src/MiniExcel/MiniExcelLibs.csproj b/src/MiniExcel/MiniExcelLibs.csproj index 31a1e630..ac07ee08 100644 --- a/src/MiniExcel/MiniExcelLibs.csproj +++ b/src/MiniExcel/MiniExcelLibs.csproj @@ -1,8 +1,8 @@  - net45;net461;netstandard2.0;net8.0;net9.0;net10.0 - 1.46.1 - 14 + net45;net461;netstandard2.0;net8.0;net9.0;net10.0;net11.0 + 1.47.0 + 15 enable MiniExcelLibs diff --git a/tests/MiniExcelTests/MiniExcelTests.csproj b/tests/MiniExcelTests/MiniExcelTests.csproj index 7b7102d5..2a2b3ea1 100644 --- a/tests/MiniExcelTests/MiniExcelTests.csproj +++ b/tests/MiniExcelTests/MiniExcelTests.csproj @@ -1,7 +1,7 @@  - net10.0 + net10.0;net11.0 enable enable From b178773539e3e30d7767ff193784958eb2c36ac6 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Tue, 10 Nov 2026 23:45:04 +0100 Subject: [PATCH 2/2] Addressed decimal conversion breaking changes introduced in .NET 11 This workaround mirrors the one in #1014. It was added to ensure backwards compatibility with the mapping of numerical values after the behaviour of conversion APIs we relied upon was altered starting .NET 11. --- src/MiniExcel/Utils/TypeHelper.cs | 51 +++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/MiniExcel/Utils/TypeHelper.cs b/src/MiniExcel/Utils/TypeHelper.cs index 2a28a059..46523e44 100644 --- a/src/MiniExcel/Utils/TypeHelper.cs +++ b/src/MiniExcel/Utils/TypeHelper.cs @@ -196,7 +196,11 @@ public static bool IsNumericType(Type type, bool isNullableUnderlyingType = fals } else if (pInfo.ExcludeNullableType == typeof(double)) // && (!Regex.IsMatch(itemValue.ToString(), @"^-?\d+(\.\d+)?([eE][-+]?\d+)?$") || itemValue.ToString().Trim().Equals("NaN"))) { - if (double.TryParse(Convert.ToString(itemValue, config.Culture), NumberStyles.Any, config.Culture, out var doubleValue)) + if (itemValue is double f64) + { + newValue = f64; + } + else if (double.TryParse(Convert.ToString(itemValue, config.Culture), NumberStyles.Any, config.Culture, out var doubleValue)) { newValue = doubleValue; } @@ -208,9 +212,36 @@ public static bool IsNumericType(Type type, bool isNullableUnderlyingType = fals : throw new InvalidCastException($"Value \"{itemValue}\" cannot be cast to double"); } } + else if (pInfo.ExcludeNullableType == typeof(decimal)) + { + if (itemValue is decimal dec) + { + newValue = dec; + } + else if (itemValue is double f64) + { +#if NET11_0_OR_GREATER + newValue = ConvertToDecimalWithLegacyApproximation(f64); +#else + newValue = Convert.ToDecimal(f64, CultureInfo.InvariantCulture); +#endif + } + else if (decimal.TryParse(Convert.ToString(itemValue, config.Culture), NumberStyles.Any, config.Culture, out var decimalValue)) + { + newValue = decimalValue; + } + else + { + var invariantString = Convert.ToString(itemValue, CultureInfo.InvariantCulture); + newValue = decimal.TryParse(invariantString, NumberStyles.Any, CultureInfo.InvariantCulture, + out var value) + ? value + : throw new InvalidCastException($"Value \"{itemValue}\" cannot be cast to decimal"); + } + } else if (pInfo.ExcludeNullableType == typeof(bool)) { - var vs = itemValue.ToString(); + var vs = itemValue?.ToString(); if (vs == "1") newValue = true; else if (vs == "0") @@ -245,6 +276,22 @@ public static bool IsNumericType(Type type, bool isNullableUnderlyingType = fals return newValue; } +#if NET11_0_OR_GREATER + /// Converts the specified double value to decimal using the same approximation applied by versions of .NET no greater than 10. + /// + /// In .NET 11 conversion APIs such as and among others + /// have been changed to approximate the resulting value more accurately. Unfortunately this is a significant breaking change for us + /// as we relied upon this truncation to discard unwanted decimal digits when mapping doubles (our default numeric type) to decimals. + /// This method is therefore a workaround to ensure backwards compatibility with previous versions of the library. + /// + private static decimal ConvertToDecimalWithLegacyApproximation(double value) + { + Span text = stackalloc char[32]; + value.TryFormat(text, out var length, "G15", CultureInfo.InvariantCulture); + return decimal.Parse(text[..length], NumberStyles.Float, CultureInfo.InvariantCulture); + } +#endif + #if !NET45 public static bool IsAsyncEnumerable(this Type type, out Type genericArgument) {