Featured image of post Numeric sorting in .NET

Numeric sorting in .NET

After 10 years since the original .NET runtime issue, numeric sorting, or natural sorting, will finally be part of .NET 10. Lets explore the new API and alternatives for older frameworks.

Numeric sorting, also often known as natural sorting, organizes strings in a human-logical order, considering numbers atomically. With default sorting algorithms, “v10” would be sorted before “v3” because the comparison happens character by character, while in numeric sorting, “v3” would be sorted before “v10” because “3” is considered smaller than “10”. This type of sorting is known to be more human-friendly and is used in applications like Windows Explorer. Let’s explore three ways to implement it in .NET.

Using .NET 10’s new CompareOptions.NumericOrdering flag

If you’re reading this post from the future (after mid-November 2025), you can use the new CompareOptions.NumericOrdering flag introduced in .NET 10 through PR #109861. For example, you can create a custom StringComparer:

var comparer = StringComparer.Create(CultureInfo.InvariantCulture, CompareOptions.NumericOrdering);

string[] tags = ["v1.0.11", "v1.0.2", "v1.0.9"];
Array.Sort(tags, comparer);

Console.WriteLine(string.Join(", ", tags)); // prints v1.0.2, v1.0.9, v1.0.11

There isn’t a public singleton StringComparer for numeric sorting, like StringComparer.Ordinal or StringComparer.InvariantCulture. This is because beyond numeric handling, you might want to customize how other characters are treated, such as case sensitivity, accents, or culture-specific rules. There are many combinations.

Additionally, it’s worth noting that the CompareOptions.NumericOrdering flag is incompatible with the CompareOptions.Ordinal and CompareOptions.OrdinalIgnoreCase flags. As explained in this issue comment, these flags were meant to be used exclusively, and significant changes would be required to support combining them.

Using the NaturalSort.Extension package

If .NET 10 isn’t available yet, or you’re targeting an older framework, you can use the NaturalSort.Extension package by Tomáš Pažourek. This package provides a NaturalSortComparer that wraps another comparer, allowing you to combine numeric sorting with other comparison options, including ordinal.

var comparer = new NaturalSortComparer(StringComparer.OrdinalIgnoreCase);

string[] tags = ["v1.0.11", "v1.0.2", "v1.0.9"];
Array.Sort(tags, comparer);

Console.WriteLine(string.Join(", ", tags)); // also prints v1.0.2, v1.0.9, v1.0.11

The test suite ensures confidence in the implementation’s quality. Performance-wise, you’ll need to evaluate its suitability for your use case. The implementation is a single file that can easily be included in your project thanks to its permissive MIT license.

Using the same Windows API as Windows Explorer

If your application targets Windows exclusively and you want to replicate the sorting behavior of Windows Explorer, you can use the StrCmpLogicalW function via P/Invoke. StrCmpLogicalW is case-insensitive and doesn’t provide the same level of customization as the previous two methods.

internal sealed class WindowsNumericStringComparer : IComparer<string?>
{
    public int Compare(string? x, string? y)
    {
        if (x == y) return 0;
        if (x == null) return -1;
        if (y == null) return 1;

        return NativeMethods.StrCmpLogical(x, y);
    }

    private static class NativeMethods
    {
        [DllImport("shlwapi.dll", EntryPoint = "StrCmpLogicalW", ExactSpelling = true, CharSet = CharSet.Unicode)]
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
        public static extern int StrCmpLogical(string psz1, string psz2);
    }
}

var comparer = new WindowsNumericStringComparer();

string[] tags = ["v1.0.11", "v1.0.2", "v1.0.9"];
Array.Sort(tags, comparer);

Console.WriteLine(string.Join(", ", tags));

If you’re using this approach, consider leveraging the Microsoft.Windows.CsWin32 package for automatic generation of P/Invoke signatures, or LibraryImportAttribute, which is also based on source generators.


Photo by Kaboompics on pexels.com

Licensed under CC BY 4.0
Ko-fi donations Buy me a coffee