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
Returns
- int
A signed integer that indicates the relative order of
xandy: less than zero whenxprecedesy, zero when they are equal under this comparer, and greater than zero whenxfollowsy.
Remarks
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
cultureCultureInfoThe culture whose comparison rules apply to non-digit segments. Must not be null.
ignoreCasebooltrue 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
cultureis null.
Equals(string?, string?)
Determines whether two strings are equal under this comparer's natural ordering.
public bool Equals(string? x, string? y)
Parameters
Returns
- bool
true when Compare(string?, string?) returns zero for
xandy; 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
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
objis 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
Returns
- int
A signed integer that indicates the relative order of
xandy, as defined by Compare(string?, string?).
Exceptions
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |