From 9436ae32e0c6989f020b407dae50ca2afa79c52c Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 18 Oct 2026 14:23:14 +0200 Subject: [PATCH 1/2] Convert hex string to byte array: move to .NET 10, bound pointer loops by span length Retarget all four projects to net10.0 and update packages (BenchmarkDotNet 0.15.8, Microsoft.NET.Test.Sdk 18.11.0, xunit 2.9.3, xunit.runner.visualstudio 4.0.1, coverlet.collector 10.1.0); remove the unused CommunityToolkit.HighPerformance. The switch, bit manipulation and lookup methods now stop at the end of the input span instead of at a null character, the lookup method's exception message no longer names the wrong character, a test covers a slice of a longer string, the console app shows the non-throwing FromHexString overload on odd-length input, and the benchmark runner passes command-line arguments. --- .../ConvertHexStringToByteArrayBenchmarks.csproj | 5 ++--- .../ConvertHexStringToByteArrayBenchmark/Program.cs | 2 +- .../ConvertHexStringToByteArrayConsole.csproj | 2 +- .../ConvertHexStringToByteArrayConsole/Program.cs | 4 ++++ .../ConversionHelpers.cs | 11 +++++++---- .../ConvertHexStringToByteArrayLibrary.csproj | 2 +- .../ConvertHexByteArrayTests.cs | 12 ++++++++++++ .../ConvertHexStringToByteArrayUnitTests.csproj | 10 +++++----- 8 files changed, 33 insertions(+), 15 deletions(-) diff --git a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/ConvertHexStringToByteArrayBenchmarks.csproj b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/ConvertHexStringToByteArrayBenchmarks.csproj index 03775a2c53..ad025981d4 100644 --- a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/ConvertHexStringToByteArrayBenchmarks.csproj +++ b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/ConvertHexStringToByteArrayBenchmarks.csproj @@ -1,13 +1,12 @@  Exe - net7.0 + net10.0 enable enable - - + diff --git a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/Program.cs b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/Program.cs index eb37090045..abf55851f2 100644 --- a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/Program.cs +++ b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayBenchmark/Program.cs @@ -1,4 +1,4 @@ using BenchmarkDotNet.Running; using ConvertHexStringToByteArray.Benchmarks; -BenchmarkRunner.Run(); \ No newline at end of file +BenchmarkRunner.Run(args: args); \ No newline at end of file diff --git a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/ConvertHexStringToByteArrayConsole.csproj b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/ConvertHexStringToByteArrayConsole.csproj index 794c118f68..356e2d662d 100644 --- a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/ConvertHexStringToByteArrayConsole.csproj +++ b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/ConvertHexStringToByteArrayConsole.csproj @@ -1,7 +1,7 @@  Exe - net7.0 + net10.0 enable enable diff --git a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/Program.cs b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/Program.cs index 80523849f6..633bba955e 100644 --- a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/Program.cs +++ b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayConsole/Program.cs @@ -17,5 +17,9 @@ result = ConversionHelpers.FromHexWithLookup(input); PrintResult(nameof(ConversionHelpers.FromHexWithLookup), result); +Span buffer = stackalloc byte[8]; +var status = Convert.FromHexString("ABC", buffer, out var charsConsumed, out var bytesWritten); +Console.WriteLine($"odd: {status} consumed={charsConsumed} written={bytesWritten}"); + static void PrintResult(string method, IEnumerable input) => Console.WriteLine($"{method}: \t[{string.Join(',', input)}]"); \ No newline at end of file diff --git a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs index 01b78950f0..228e42382c 100644 --- a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs +++ b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs @@ -53,7 +53,8 @@ public static unsafe byte[] FromHexWithSwitchComputation(ReadOnlySpan inpu { var destPtr = d; var srcPtr = s; - while (*srcPtr != 0) + var srcEnd = s + input.Length; + while (srcPtr < srcEnd) { var result = (byte) (ComputeNibbleFromHexChar(*srcPtr++) << 4); result |= ComputeNibbleFromHexChar(*srcPtr++); @@ -89,7 +90,8 @@ public static unsafe byte[] FromHexWithBitManipulation(ReadOnlySpan input) { var sPtr = &srcPtr[0]; var dPtr = &destPtr[0]; - while (*sPtr != 0) + var sEnd = sPtr + input.Length; + while (sPtr < sEnd) { var hi = PerformBitManipulation(*sPtr++); var lo = PerformBitManipulation(*sPtr++) & 0x0F; @@ -127,14 +129,15 @@ public static unsafe byte[] FromHexWithLookup(ReadOnlySpan input) { var s = &inputRef[0]; var d = destRef; + var end = s + input.Length; - while (*s != 0) + while (s < end) { byte lowValue; if (*s > 102 || (*d = hiRef[*s++]) == 255 || *s > 102 || (lowValue = lowRef[*s++]) == 255 ) - throw new ArgumentException($"Invalid character found in string: '{*s}'", nameof(input)); + throw new ArgumentException("Input contains a non-hex character", nameof(input)); *d++ += lowValue; } diff --git a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConvertHexStringToByteArrayLibrary.csproj b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConvertHexStringToByteArrayLibrary.csproj index d3971e43df..8186353f28 100644 --- a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConvertHexStringToByteArrayLibrary.csproj +++ b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConvertHexStringToByteArrayLibrary.csproj @@ -1,6 +1,6 @@  - net7.0 + net10.0 enable enable true diff --git a/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexByteArrayTests.cs b/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexByteArrayTests.cs index 8be64b5f5a..f24640fa4b 100644 --- a/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexByteArrayTests.cs +++ b/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexByteArrayTests.cs @@ -142,4 +142,16 @@ public void GivenLookupMethod_WhenInvalidHexDataIsProvided_ThenThrowsArgumentExc { Assert.Throws(() => ConversionHelpers.FromHexWithLookup(source)); } + [Fact] + public void GivenSliceOfLongerString_WhenConverting_ThenEveryMethodStopsAtTheEndOfTheSlice() + { + var slice = "DEADZZ".AsSpan(0, 4); + var expected = new byte[] {222, 173}; + + Assert.Equal(expected, ConversionHelpers.FromHexWithModularArithmetic(slice)); + Assert.Equal(expected, ConversionHelpers.FromHexWithSwitchComputation(slice)); + Assert.Equal(expected, ConversionHelpers.FromHexWithBitManipulation(slice)); + Assert.Equal(expected, ConversionHelpers.FromHexWithConvert(slice)); + Assert.Equal(expected, ConversionHelpers.FromHexWithLookup(slice)); + } } \ No newline at end of file diff --git a/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexStringToByteArrayUnitTests.csproj b/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexStringToByteArrayUnitTests.csproj index 1a5e6ef094..611898a078 100644 --- a/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexStringToByteArrayUnitTests.csproj +++ b/collections-arrays/ConvertHexStringToByteArray/test/ConvertHexStringToByteArrayUnitTests/ConvertHexStringToByteArrayUnitTests.csproj @@ -1,19 +1,19 @@  - net7.0 + net10.0 enable enable false true - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 7b72a9788897febe6c39362393ee205dbc554a88 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 18 Oct 2026 14:29:03 +0200 Subject: [PATCH 2/2] Convert hex string to byte array: align two lines with the article's formatting Whitespace only: a space after the cast in FromHexWithModularArithmetic and no space before the closing parenthesis of the lookup check, matching the snippets the article prints. --- .../ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs index 228e42382c..9f47647461 100644 --- a/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs +++ b/collections-arrays/ConvertHexStringToByteArray/src/ConvertHexStringToByteArrayLibrary/ConversionHelpers.cs @@ -20,7 +20,7 @@ public static byte[] FromHexWithModularArithmetic(ReadOnlySpan input) var dest = new byte[input.Length >> 1]; for (int i = 0, j = 0; j < dest.Length; j++) - dest[j] = (byte)((PerformModularArithmeticCalculation(input[i++]) << 4) + + dest[j] = (byte) ((PerformModularArithmeticCalculation(input[i++]) << 4) + PerformModularArithmeticCalculation(input[i++])); return dest; @@ -136,7 +136,7 @@ public static unsafe byte[] FromHexWithLookup(ReadOnlySpan input) byte lowValue; if (*s > 102 || (*d = hiRef[*s++]) == 255 || - *s > 102 || (lowValue = lowRef[*s++]) == 255 ) + *s > 102 || (lowValue = lowRef[*s++]) == 255) throw new ArgumentException("Input contains a non-hex character", nameof(input)); *d++ += lowValue;