Table of Contents

NaturalStringComparer Class

Definition

Namespace
Bodu.Extensions
Assembly
Bodu.Core.dll
Package
Bodu.Core 1.0.1
Source
NaturalStringComparer.cs

Provides a numeric-aware ("natural") string comparer that orders embedded runs of ASCII digits by numeric value rather than character by character, so that "file2" sorts before "file10".

public sealed class NaturalStringComparer : IComparer<string?>, IComparer, IEqualityComparer<string?>
Inheritance
NaturalStringComparer
Implements
Inherited Members
Extension Methods

Examples

var names = new[] { "file10", "file2", "file1" };

Array.Sort(names, NaturalStringComparer.Ordinal);
// names is now ["file1", "file2", "file10"] - a plain ordinal sort would yield
// ["file1", "file10", "file2"].

Remarks

The comparer mirrors the ordering produced by Windows Explorer (StrCmpLogicalW) and Python's natsort package: each string is treated as an alternating sequence of digit runs and non-digit segments. Digit runs are compared by numeric magnitude - leading zeros are skipped, a longer trimmed run is greater, and equal-length trimmed runs are compared digit by digit - so runs of arbitrary length compare correctly without ever being parsed into a bounded integer type. Non-digit segments are compared ordinally ( Ordinal / OrdinalIgnoreCase) or using a culture's CompareInfo (CurrentCulture / CurrentCultureIgnoreCase / Create(CultureInfo, bool)).

To keep the order total and deterministic, two digit runs with the same numeric value but different leading-zero counts do not compare equal: the first such difference is recorded and, when the strings are otherwise equal, the run with fewer leading zeros sorts first ("7" before "07"). A digit run compared against a non-digit character at the same position sorts first (numbers sort before text), and a string that ends where the other continues sorts first ("file" before "file1"). null sorts before any non-null string, two null references are equal, and the empty string sorts before any non-empty string.

Only the ASCII digits '0'-'9' participate in numeric comparison; other Unicode digit classes are treated as ordinary text. A leading '-' is an ordinary character (no sign parsing), and decimal points, thousands separators, and version-sort dotted-tuple semantics are likewise out of scope - '.' simply separates adjacent digit runs.

Instances are stateless and thread-safe. The CurrentCulture and CurrentCultureIgnoreCase comparers capture the comparison behaviour, not a culture snapshot: each comparison reads CurrentCulture at the time it executes, mirroring CurrentCulture. A comparer returned by Create(CultureInfo, bool) captures the supplied culture instead.

Properties

CurrentCulture

Gets a NaturalStringComparer that compares non-digit segments using culture-sensitive, case-sensitive comparison rules read from CurrentCulture at comparison time.

public static NaturalStringComparer CurrentCulture { get; }

Property Value

NaturalStringComparer

A natural comparer whose text comparison follows the culture current when each comparison executes.

Remarks

The returned comparer captures the behaviour, not a culture snapshot: like CurrentCulture, each call reads CurrentCulture when the comparison is performed. Use Create(CultureInfo, bool) to capture a specific culture.

CurrentCultureIgnoreCase

Gets a NaturalStringComparer that compares non-digit segments using culture-sensitive comparison rules read from CurrentCulture at comparison time, ignoring case.

public static NaturalStringComparer CurrentCultureIgnoreCase { get; }

Property Value

NaturalStringComparer

A natural comparer whose case-insensitive text comparison follows the culture current when each comparison executes.

Remarks

The returned comparer captures the behaviour, not a culture snapshot: like CurrentCultureIgnoreCase, each call reads CurrentCulture when the comparison is performed. Use Create(CultureInfo, bool) to capture a specific culture.

Ordinal

Gets a NaturalStringComparer that compares non-digit segments using ordinal (binary) case-sensitive comparison.

public static NaturalStringComparer Ordinal { get; }

Property Value

NaturalStringComparer

A stateless, thread-safe natural comparer with ordinal, case-sensitive text comparison.

OrdinalIgnoreCase

Gets a NaturalStringComparer that compares non-digit segments using ordinal (binary) comparison, ignoring case.

public static NaturalStringComparer OrdinalIgnoreCase { get; }

Property Value

NaturalStringComparer

A stateless, thread-safe natural comparer with ordinal, case-insensitive text comparison.

Methods

Compare(string?, string?)

Compares two strings using natural (numeric-aware) ordering and returns an indication of their relative order.

public int Compare(string? x, string? y)

Parameters

x string

The first string to compare.

y string

The second string to compare.

Returns

int

A signed integer that indicates the relative order of x and y: less than zero when x precedes y, zero when they are equal under this comparer, and greater than zero when x follows y.

Remarks

null sorts before any non-null string and two null references compare equal. See the class-level remarks for the digit-run comparison rules and the leading-zero tiebreak that keeps the order total.

Create(CultureInfo, bool)

Creates a NaturalStringComparer that compares non-digit segments using the comparison rules of the specified culture.

public static NaturalStringComparer Create(CultureInfo culture, bool ignoreCase)

Parameters

culture CultureInfo

The culture whose comparison rules apply to non-digit segments. Must not be null.

ignoreCase bool

true to ignore case when comparing non-digit segments; otherwise false.

Returns

NaturalStringComparer

A new comparer bound to the supplied culture.

Remarks

Unlike CurrentCulture and CurrentCultureIgnoreCase, the returned comparer captures culture and is unaffected by later changes to CurrentCulture.

Exceptions

ArgumentNullException

Thrown if culture is null.

Equals(string?, string?)

Determines whether two strings are equal under this comparer's natural ordering.

public bool Equals(string? x, string? y)

Parameters

x string

The first string to compare.

y string

The second string to compare.

Returns

bool

true when Compare(string?, string?) returns zero for x and y; otherwise false.

Remarks

Because of the leading-zero tiebreak, two strings whose digit runs differ only in leading zeros (for example "file07" and "file7") are not equal. Equality therefore requires every digit run to match character for character and every non-digit segment to be equal under the configured text comparison.

GetHashCode(string?)

Returns a hash code for the specified string that is consistent with Equals(string?, string?).

public int GetHashCode(string? obj)

Parameters

obj string

The string for which to compute a hash code. Must not be null.

Returns

int

A hash code such that any two strings equal under this comparer produce the same value.

Remarks

For the ordinal modes, equality under this comparer coincides with ordinal (or ordinal case-insensitive) string equality, so the hash is the string's own ordinal hash. For culture-aware modes, the hash is composed from each digit run (hashed verbatim) and each non-digit segment's GetHashCode(ReadOnlySpan<char>, CompareOptions) value; segments that hash like the empty string (fully ignorable text) are skipped so strings differing only by ignorable characters hash identically.

The culture-aware composition trades hash distribution for correctness: distinct strings may collide more often than under an ordinal hash, but equal strings are always assigned equal hash codes.

Exceptions

ArgumentNullException

Thrown if obj is null.

Explicit Interface Implementations

IComparer.Compare(object, object)

Compares two objects using natural (numeric-aware) ordering and returns an indication of their relative order.

int IComparer.Compare(object x, object y)

Parameters

x object

The first object to compare. Must be a string or null.

y object

The second object to compare. Must be a string or null.

Returns

int

A signed integer that indicates the relative order of x and y, as defined by Compare(string?, string?).

Exceptions

ArgumentException

Thrown if x or y is neither a string nor null.

Applies to

ProductVersions
.NET8, 10