diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 4767f7a..23af429 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,4 +1,5 @@
-# build.yml v1.4
+# build.yml v1.5
+# 1.5 - Use prerelease ProjProps.
# 1.4 - Avoid set-env.
# 1.3 - Include tag workflow in this file.
# 1.2 - Define DOTNET_SKIP_FIRST_TIME_EXPERIENCE/NUGET_XMLDOC_MODE.
@@ -52,8 +53,8 @@ jobs:
- name: Get current version
run: |
- dotnet tool install --global Nito.ProjProps
- echo "NEWTAG=v$(projprops --name version --output-format SingleValueOnly --project src --project-search)" >> $GITHUB_ENV
+ dotnet tool install --global Nito.ProjProps --version 2.0.0-pre01
+ echo "NEWTAG=v$(projprops --name version --project src)" >> $GITHUB_ENV
- name: Build
run: |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a008789..08e3ace 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
# Changelog
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [6.3.0] -
+### Added
+- More nullable reference type support.
+- Natural string comparers that compare numeric substrings as numeric values.
+ - Note: `GetHashCode` for natural string comparers will allocate memory on all platforms older than .NET Core 3.0, including all versions of .NET Framework.
+ - Note: `GetHashCode` will cause extra collisions when used with invariant cultures on platforms that only support .NET Standard 1.x (except .NET Framework). This means Xamarin.Android 7.1, Xamarin.iOS 10.8, and Xamarin.Mac 3.0 will have pathologically inefficient `GetHashCode` implementations for natural string comparers using an invariant culture. Xamarin.Android 8.0+, Xamarin.iOS 10.14+, and Xamarin.Mac 3.8+ will work properly.
+
## [6.2.2] - 2021-09-25
### Changed
- Bump Rx and Ix dependencies.
diff --git a/future/StringSpanComparer.cs b/future/StringSpanComparer.cs
new file mode 100644
index 0000000..0ea0c09
--- /dev/null
+++ b/future/StringSpanComparer.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Text;
+
+namespace Nito.Comparers.Util
+{
+ ///
+ /// A type that can compare strings as well as read-only spans of characters.
+ ///
+ public sealed class StringSpanComparer : IFullComparer
+ {
+ private readonly CompareInfo _compareInfo;
+ private readonly CompareOptions _options;
+
+ ///
+ /// Creates a new instance using the specified compare info and options.
+ ///
+ public StringSpanComparer(CompareInfo compareInfo, CompareOptions options)
+ {
+ _compareInfo = compareInfo;
+ _options = options;
+ }
+
+ ///
+ /// Creates a new instance using the specified string comparison.
+ ///
+ public StringSpanComparer(StringComparison comparison)
+ : this(GetCompareInfo(comparison), GetCompareOptions(comparison))
+ {
+ }
+
+ private static CompareInfo GetCompareInfo(StringComparison comparison)
+ {
+ return comparison switch
+ {
+ StringComparison.CurrentCulture => CultureInfo.CurrentCulture.CompareInfo,
+ StringComparison.CurrentCultureIgnoreCase => CultureInfo.CurrentCulture.CompareInfo,
+ (StringComparison)2 /* InvariantCulture */ => CultureInfo.InvariantCulture.CompareInfo,
+ (StringComparison)3 /* InvariantCultureIgnoreCase */ => CultureInfo.InvariantCulture.CompareInfo,
+ StringComparison.Ordinal => CultureInfo.InvariantCulture.CompareInfo,
+ StringComparison.OrdinalIgnoreCase => CultureInfo.InvariantCulture.CompareInfo,
+ _ => throw new ArgumentException($"The string comparison type {comparison} is not supported.", nameof(comparison)),
+ };
+ }
+
+ private static CompareOptions GetCompareOptions(StringComparison comparison)
+ {
+ return comparison switch
+ {
+ StringComparison.CurrentCulture => CompareOptions.None,
+ StringComparison.CurrentCultureIgnoreCase => CompareOptions.IgnoreCase,
+ (StringComparison)2 /* InvariantCulture */ => CompareOptions.None,
+ (StringComparison)3 /* InvariantCultureIgnoreCase */ => CompareOptions.IgnoreCase,
+ StringComparison.Ordinal => CompareOptions.Ordinal,
+ StringComparison.OrdinalIgnoreCase => CompareOptions.OrdinalIgnoreCase,
+ _ => throw new ArgumentException($"The string comparison type {comparison} is not supported.", nameof(comparison)),
+ };
+ }
+
+ ///
+ /// Compares two read-only spans of characters as though they were strings.
+ ///
+ public int Compare(ReadOnlySpan x, ReadOnlySpan y) => _compareInfo.Compare(x, y, _options);
+
+ ///
+ /// Determines whether two read-only spans of characters are equal, as though they were strings.
+ ///
+ public bool Equals(ReadOnlySpan x, ReadOnlySpan y) => Compare(x, y) == 0;
+
+ ///
+ /// Gets a hash code for a read-only span of characters, as though it were a string.
+ ///
+ public int GetHashCode(ReadOnlySpan obj) => _compareInfo.GetHashCode(obj, _options);
+
+ ///
+ public int Compare(string? x, string? y)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public bool Equals(string? x, string? y) => EqualityComparerHelpers.ImplementEquals(x, y, false, DoEquals!);
+
+ private bool DoEquals(string? x, string? y) => Equals(x == null ? default : x.AsSpan(), y == null ? default : y.AsSpan());
+
+ ///
+ public int GetHashCode(string? obj) => EqualityComparerHelpers.ImplementGetHashCode(obj, false, DoGetHashCode!);
+
+ private int DoGetHashCode(string? obj) => GetHashCode(obj == null ? default : obj.AsSpan());
+
+ int IComparer.Compare(object? x, object? y)
+ {
+ throw new NotImplementedException();
+ }
+
+ bool IEqualityComparer.Equals(object? x, object? y) => EqualityComparerHelpers.ImplementEquals(x, y, false, DoEquals);
+
+ int IEqualityComparer.GetHashCode(object? obj) => EqualityComparerHelpers.ImplementGetHashCode(obj, false, DoGetHashCode);
+ }
+}
diff --git a/src/Comparers.Ix/Comparers.Ix.csproj b/src/Comparers.Ix/Comparers.Ix.csproj
index 559658b..f342d1b 100644
--- a/src/Comparers.Ix/Comparers.Ix.csproj
+++ b/src/Comparers.Ix/Comparers.Ix.csproj
@@ -2,7 +2,7 @@
This old package just forwards to Nito.Comparers.Ix.
- netstandard1.0;netstandard2.0;net461
+ netstandard1.0;netstandard2.0;net45;net461
comparer;equalitycomparer;icomparable;iequatable
true
diff --git a/src/Comparers.Rx/Comparers.Rx.csproj b/src/Comparers.Rx/Comparers.Rx.csproj
index 8b6cc67..dfa1606 100644
--- a/src/Comparers.Rx/Comparers.Rx.csproj
+++ b/src/Comparers.Rx/Comparers.Rx.csproj
@@ -2,7 +2,7 @@
This old package just forwards to Nito.Comparers.Rx.
- netstandard1.0;netstandard2.0;net461
+ netstandard1.0;netstandard2.0;net45;net461
comparer;equalitycomparer;icomparable;iequatable
true
diff --git a/src/Comparers/Comparers.csproj b/src/Comparers/Comparers.csproj
index df99963..f090ddd 100644
--- a/src/Comparers/Comparers.csproj
+++ b/src/Comparers/Comparers.csproj
@@ -1,8 +1,8 @@
-
+
This old package just forwards to Nito.Comparers.
- netstandard1.0;netstandard1.3;netstandard2.0;net461;net472;netcoreapp2.0
+ netstandard1.0;netstandard1.3;netstandard2.0;net45;net461;net472;netcoreapp2.0
comparer;equalitycomparer;icomparable;iequatable
true
diff --git a/src/Nito.Comparers.Core/Advanced/AdvancedComparerBase.cs b/src/Nito.Comparers.Core/Advanced/AdvancedComparerBase.cs
index 0e5c8a0..a32b8a8 100644
--- a/src/Nito.Comparers.Core/Advanced/AdvancedComparerBase.cs
+++ b/src/Nito.Comparers.Core/Advanced/AdvancedComparerBase.cs
@@ -42,7 +42,7 @@ protected AdvancedComparerBase(bool specialNullHandling)
///
/// The object for which to return a hash code. May be null if is true.
/// A hash code for the specified object.
- protected abstract int DoGetHashCode(T obj);
+ protected abstract int DoGetHashCode(T? obj);
///
/// Compares two objects and returns a value less than 0 if is less than , 0 if is equal to , or greater than 0 if is greater than .
@@ -50,22 +50,22 @@ protected AdvancedComparerBase(bool specialNullHandling)
/// The first object to compare. May be null if is true.
/// The second object to compare. May be null if is true.
/// A value less than 0 if is less than , 0 if is equal to , or greater than 0 if is greater than .
- protected abstract int DoCompare(T x, T y);
+ protected abstract int DoCompare(T? x, T? y);
///
- public int Compare(T x, T y) => ((IComparer)_implementation).Compare(x, y);
+ public int Compare(T? x, T? y) => ((IComparer)_implementation).Compare(x!, y!);
///
- public bool Equals(T x, T y) => ((IEqualityComparer)_implementation).Equals(x, y);
+ public bool Equals(T? x, T? y) => ((IEqualityComparer)_implementation).Equals(x!, y!);
///
- public int GetHashCode(T obj) => ((IEqualityComparer)_implementation).GetHashCode(obj);
+ public int GetHashCode(T? obj) => ((IEqualityComparer)_implementation).GetHashCode(obj!);
- int IComparer.Compare(object x, object y) => ((IComparer)_implementation).Compare(x, y);
+ int IComparer.Compare(object? x, object? y) => ((IComparer)_implementation).Compare(x, y);
- bool IEqualityComparer.Equals(object x, object y) => ((IEqualityComparer)_implementation).Equals(x, y);
+ bool IEqualityComparer.Equals(object? x, object? y) => ((IEqualityComparer)_implementation).Equals(x, y);
- int IEqualityComparer.GetHashCode(object obj) => ((IEqualityComparer)_implementation).GetHashCode(obj);
+ int IEqualityComparer.GetHashCode(object? obj) => ((IEqualityComparer)_implementation).GetHashCode(obj!);
private sealed class Implementation : ComparerBase
{
@@ -79,9 +79,9 @@ public Implementation(bool specialNullHandling, AdvancedComparerBase parent)
public bool SpecialNullHandlingValue => SpecialNullHandling;
- protected override int DoGetHashCode(T obj) => _parent.DoGetHashCode(obj);
+ protected override int DoGetHashCode(T? obj) => _parent.DoGetHashCode(obj);
- protected override int DoCompare(T x, T y) => _parent.DoCompare(x, y);
+ protected override int DoCompare(T? x, T? y) => _parent.DoCompare(x, y);
}
}
}
diff --git a/src/Nito.Comparers.Core/Advanced/AdvancedEqualityComparerBase.cs b/src/Nito.Comparers.Core/Advanced/AdvancedEqualityComparerBase.cs
index 015e3de..7dcfd95 100644
--- a/src/Nito.Comparers.Core/Advanced/AdvancedEqualityComparerBase.cs
+++ b/src/Nito.Comparers.Core/Advanced/AdvancedEqualityComparerBase.cs
@@ -42,7 +42,7 @@ protected AdvancedEqualityComparerBase(bool specialNullHandling)
///
/// The object for which to return a hash code. May be null if is true.
/// A hash code for the specified object.
- protected abstract int DoGetHashCode(T obj);
+ protected abstract int DoGetHashCode(T? obj);
///
/// Compares two objects and returns true if they are equal and false if they are not equal.
@@ -50,17 +50,17 @@ protected AdvancedEqualityComparerBase(bool specialNullHandling)
/// The first object to compare. May be null if is true.
/// The second object to compare. May be null if is true.
/// true if is equal to ; otherwise, false.
- protected abstract bool DoEquals(T x, T y);
+ protected abstract bool DoEquals(T? x, T? y);
///
- public bool Equals(T x, T y) => ((IEqualityComparer)_implementation).Equals(x, y);
+ public bool Equals(T? x, T? y) => ((IEqualityComparer)_implementation).Equals(x!, y!);
///
- public int GetHashCode(T obj) => ((IEqualityComparer)_implementation).GetHashCode(obj);
+ public int GetHashCode(T? obj) => ((IEqualityComparer)_implementation).GetHashCode(obj!);
- bool IEqualityComparer.Equals(object x, object y) => ((IEqualityComparer)_implementation).Equals(x, y);
+ bool IEqualityComparer.Equals(object? x, object? y) => ((IEqualityComparer)_implementation).Equals(x, y);
- int IEqualityComparer.GetHashCode(object obj) => ((IEqualityComparer)_implementation).GetHashCode(obj);
+ int IEqualityComparer.GetHashCode(object? obj) => ((IEqualityComparer)_implementation).GetHashCode(obj!);
private sealed class Implementation : EqualityComparerBase
{
@@ -74,9 +74,9 @@ public Implementation(bool specialNullHandling, AdvancedEqualityComparerBase
public bool SpecialNullHandlingValue => SpecialNullHandling;
- protected override int DoGetHashCode(T obj) => _parent.DoGetHashCode(obj);
+ protected override int DoGetHashCode(T? obj) => _parent.DoGetHashCode(obj);
- protected override bool DoEquals(T x, T y) => _parent.DoEquals(x, y);
+ protected override bool DoEquals(T? x, T? y) => _parent.DoEquals(x, y);
}
}
}
diff --git a/src/Nito.Comparers.Core/ComparableBase.cs b/src/Nito.Comparers.Core/ComparableBase.cs
index e694feb..c370455 100644
--- a/src/Nito.Comparers.Core/ComparableBase.cs
+++ b/src/Nito.Comparers.Core/ComparableBase.cs
@@ -36,7 +36,7 @@ public abstract class ComparableBase : IEquatable, IComparable, IComparabl
///
/// The object to compare with this instance. May be null.
/// A value indicating whether this instance is equal to the specified object.
- public bool Equals(T other) => ComparableImplementations.ImplementEquals(DefaultComparer, (T)this, other!);
+ public bool Equals(T? other) => ComparableImplementations.ImplementEquals(DefaultComparer, (T)this, other!);
///
/// Returns a value indicating the relative order of this instance and the specified object: a negative value if this instance is less than the specified object; zero if this instance is equal to the specified object; and a positive value if this instance is greater than the specified object.
@@ -50,6 +50,6 @@ public abstract class ComparableBase : IEquatable, IComparable, IComparabl
///
/// The object to compare with this instance. May be null.
/// A value indicating the relative order of this instance and the specified object: a negative value if this instance is less than the specified object; zero if this instance is equal to the specified object; and a positive value if this instance is greater than the specified object.
- public int CompareTo(T other) => ComparableImplementations.ImplementCompareTo(DefaultComparer, (T)this, other!);
+ public int CompareTo(T? other) => ComparableImplementations.ImplementCompareTo(DefaultComparer, (T)this, other!);
}
}
diff --git a/src/Nito.Comparers.Core/ComparerBuilderFor.cs b/src/Nito.Comparers.Core/ComparerBuilderFor.cs
index 51f01b6..ebd0e5c 100644
--- a/src/Nito.Comparers.Core/ComparerBuilderFor.cs
+++ b/src/Nito.Comparers.Core/ComparerBuilderFor.cs
@@ -3,6 +3,8 @@
using System.ComponentModel;
using Nito.Comparers.Util;
+#pragma warning disable IDE0060
+
namespace Nito.Comparers
{
///
@@ -35,6 +37,13 @@ public static class ComparerBuilderForExtensions
///
public static IFullComparer Default(this ComparerBuilderFor @this) => (IFullComparer)ComparerHelpers.NormalizeDefault(null);
+ ///
+ /// Gets a natural string comparer, which treats numeric sequences (0-9) as numeric.
+ ///
+ /// The comparer builder.
+ /// The comparison type used to compare the text segments of the string (not used for numeric segments).
+ public static IFullComparer Natural(this ComparerBuilderFor @this, StringComparison comparison) => new NaturalStringComparer(comparison);
+
///
/// Creates a key comparer.
///
@@ -46,7 +55,7 @@ public static class ComparerBuilderForExtensions
/// A value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A value indicating whether the sorting is done in descending order. If false (the default), then the sort is in ascending order.
/// A key comparer.
- public static IFullComparer OrderBy(this ComparerBuilderFor @this, Func selector, Func, IComparer> comparerFactory, bool specialNullHandling = false, bool descending = false)
+ public static IFullComparer OrderBy(this ComparerBuilderFor @this, Func selector, Func, IComparer> comparerFactory, bool specialNullHandling = false, bool descending = false)
{
_ = comparerFactory ?? throw new ArgumentNullException(nameof(comparerFactory));
var comparer = comparerFactory(ComparerBuilder.For());
@@ -64,7 +73,7 @@ public static IFullComparer OrderBy(this ComparerBuilderFor @this
/// A value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A value indicating whether the sorting is done in descending order. If false (the default), then the sort is in ascending order.
/// A key comparer.
- public static IFullComparer OrderBy(this ComparerBuilderFor @this, Func selector, IComparer? keyComparer = null, bool specialNullHandling = false, bool descending = false)
+ public static IFullComparer OrderBy(this ComparerBuilderFor @this, Func selector, IComparer? keyComparer = null, bool specialNullHandling = false, bool descending = false)
{
var selectComparer = new SelectComparer(selector, keyComparer, specialNullHandling);
return descending ? selectComparer.Reverse() : selectComparer;
diff --git a/src/Nito.Comparers.Core/ComparerExtensions.cs b/src/Nito.Comparers.Core/ComparerExtensions.cs
index f819cd2..36e6b8f 100644
--- a/src/Nito.Comparers.Core/ComparerExtensions.cs
+++ b/src/Nito.Comparers.Core/ComparerExtensions.cs
@@ -39,7 +39,7 @@ public static IFullComparer ThenBy(this IComparer? source, IComparer
/// A value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A value indicating whether the sorting is done in descending order. If false (the default), then the sort is in ascending order.
/// A comparer that uses a key comparer if the source comparer determines the objects are equal.
- public static IFullComparer ThenBy(this IComparer? source, Func selector, Func, IComparer> comparerFactory, bool specialNullHandling = false, bool descending = false)
+ public static IFullComparer ThenBy(this IComparer? source, Func selector, Func, IComparer> comparerFactory, bool specialNullHandling = false, bool descending = false)
{
_ = comparerFactory ?? throw new ArgumentNullException(nameof(comparerFactory));
var comparer = comparerFactory(ComparerBuilder.For());
@@ -57,7 +57,7 @@ public static IFullComparer ThenBy(this IComparer? source, FuncA value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A value indicating whether the sorting is done in descending order. If false (the default), then the sort is in ascending order.
/// A comparer that uses a key comparer if the source comparer determines the objects are equal.
- public static IFullComparer ThenBy(this IComparer? source, Func selector, IComparer? keyComparer = null, bool specialNullHandling = false, bool descending = false)
+ public static IFullComparer ThenBy(this IComparer? source, Func selector, IComparer? keyComparer = null, bool specialNullHandling = false, bool descending = false)
{
var selectComparer = new SelectComparer(selector, keyComparer, specialNullHandling);
return source.ThenBy(selectComparer, descending);
diff --git a/src/Nito.Comparers.Core/EqualityComparerBuilderFor.cs b/src/Nito.Comparers.Core/EqualityComparerBuilderFor.cs
index 3da81a6..792c592 100644
--- a/src/Nito.Comparers.Core/EqualityComparerBuilderFor.cs
+++ b/src/Nito.Comparers.Core/EqualityComparerBuilderFor.cs
@@ -3,6 +3,9 @@
using System.ComponentModel;
using Nito.Comparers.Util;
+#pragma warning disable IDE0079
+#pragma warning disable IDE0060
+
namespace Nito.Comparers
{
///
@@ -35,6 +38,13 @@ public static class EqualityComparerBuilderForExtensions
///
public static IFullEqualityComparer Default(this EqualityComparerBuilderFor @this) => (IFullEqualityComparer)EqualityComparerHelpers.NormalizeDefault(null);
+ ///
+ /// Gets a natural string equality comparer, which treats numeric sequences (0-9) as numeric.
+ ///
+ /// The equality comparer builder.
+ /// The comparison type used to compare the text segments of the string (not used for numeric segments).
+ public static IFullEqualityComparer Natural(this EqualityComparerBuilderFor @this, StringComparison comparison) => new NaturalStringComparer(comparison);
+
///
/// Gets the reference equality comparer for this type.
///
@@ -54,7 +64,7 @@ public static IFullEqualityComparer Reference(this EqualityComparerBuilder
/// The definition of the key comparer. May not be null.
/// A value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A key comparer.
- public static IFullEqualityComparer EquateBy(this EqualityComparerBuilderFor @this, Func selector, Func, IEqualityComparer> comparerFactory, bool specialNullHandling = false)
+ public static IFullEqualityComparer EquateBy(this EqualityComparerBuilderFor @this, Func selector, Func, IEqualityComparer> comparerFactory, bool specialNullHandling = false)
{
_ = comparerFactory ?? throw new ArgumentNullException(nameof(comparerFactory));
var comparer = comparerFactory(EqualityComparerBuilder.For());
@@ -71,7 +81,7 @@ public static IFullEqualityComparer EquateBy(this EqualityComparerBu
/// The key comparer. Defaults to null. If this is null, the default comparer is used.
/// A value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A key comparer.
- public static IFullEqualityComparer EquateBy(this EqualityComparerBuilderFor @this, Func selector, IEqualityComparer? keyComparer = null, bool specialNullHandling = false)
+ public static IFullEqualityComparer EquateBy(this EqualityComparerBuilderFor @this, Func selector, IEqualityComparer? keyComparer = null, bool specialNullHandling = false)
{
return new SelectEqualityComparer(selector, keyComparer, specialNullHandling);
}
diff --git a/src/Nito.Comparers.Core/EqualityComparerExtensions.cs b/src/Nito.Comparers.Core/EqualityComparerExtensions.cs
index d2f295a..580848b 100644
--- a/src/Nito.Comparers.Core/EqualityComparerExtensions.cs
+++ b/src/Nito.Comparers.Core/EqualityComparerExtensions.cs
@@ -29,7 +29,7 @@ public static IFullEqualityComparer ThenEquateBy(this IEqualityComparer
/// The definition of the key comparer. May not be null.
/// A value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A comparer that uses a key comparer if the source comparer determines the objects are equal.
- public static IFullEqualityComparer ThenEquateBy(this IEqualityComparer? source, Func selector, Func, IEqualityComparer> comparerFactory, bool specialNullHandling = false)
+ public static IFullEqualityComparer ThenEquateBy(this IEqualityComparer? source, Func selector, Func, IEqualityComparer> comparerFactory, bool specialNullHandling = false)
{
_ = comparerFactory ?? throw new ArgumentNullException(nameof(comparerFactory));
var comparer = comparerFactory(EqualityComparerBuilder.For());
@@ -46,7 +46,7 @@ public static IFullEqualityComparer ThenEquateBy(this IEqualityCompa
/// The key comparer. Defaults to null. If this is null, the default comparer is used.
/// A value indicating whether null values are passed to . If false, then null values are considered less than any non-null values and are not passed to . This value is ignored if is a non-nullable type.
/// A comparer that uses a key comparer if the source comparer determines the objects are equal.
- public static IFullEqualityComparer ThenEquateBy(this IEqualityComparer? source, Func selector, IEqualityComparer? keyComparer = null, bool specialNullHandling = false)
+ public static IFullEqualityComparer ThenEquateBy(this IEqualityComparer? source, Func selector, IEqualityComparer? keyComparer = null, bool specialNullHandling = false)
{
var selectComparer = new SelectEqualityComparer(selector, keyComparer, specialNullHandling);
return source.ThenEquateBy(selectComparer);
diff --git a/src/Nito.Comparers.Core/EquatableBase.cs b/src/Nito.Comparers.Core/EquatableBase.cs
index 2372d1f..62b2d05 100644
--- a/src/Nito.Comparers.Core/EquatableBase.cs
+++ b/src/Nito.Comparers.Core/EquatableBase.cs
@@ -36,6 +36,6 @@ public abstract class EquatableBase : IEquatable where T : EquatableBase
/// The object to compare with this instance. May be null.
/// A value indicating whether this instance is equal to the specified object.
- public bool Equals(T other) => ComparableImplementations.ImplementEquals(DefaultComparer, (T)this, other!);
+ public bool Equals(T? other) => ComparableImplementations.ImplementEquals(DefaultComparer, (T)this, other);
}
}
diff --git a/src/Nito.Comparers.Core/Fixes/ExplicitGetHashCodeComparer.cs b/src/Nito.Comparers.Core/Fixes/ExplicitGetHashCodeComparer.cs
index f56f450..97bd966 100644
--- a/src/Nito.Comparers.Core/Fixes/ExplicitGetHashCodeComparer.cs
+++ b/src/Nito.Comparers.Core/Fixes/ExplicitGetHashCodeComparer.cs
@@ -15,16 +15,16 @@ internal sealed class ExplicitGetHashCodeComparer : SourceComparerBase
///
/// The source comparer. If this is null, the default comparer is used.
/// The GetHashCode implementation to use. If this is null, this type will attempt to find GetHashCode on ; if none is found, throws an exception.
- public ExplicitGetHashCodeComparer(IComparer? source, Func? getHashCode)
+ public ExplicitGetHashCodeComparer(IComparer? source, Func? getHashCode)
: base(source, getHashCode, false)
{
}
///
- protected override int DoGetHashCode(T obj) => SourceGetHashCode(obj);
+ protected override int DoGetHashCode(T? obj) => SourceGetHashCode(obj);
///
- protected override int DoCompare(T x, T y) => Source.Compare(x, y);
+ protected override int DoCompare(T? x, T? y) => Source.Compare(x!, y!);
///
/// Returns a short, human-readable description of the comparer. This is intended for debugging and not for other purposes.
diff --git a/src/Nito.Comparers.Core/Fixes/FixComparerExtensions.cs b/src/Nito.Comparers.Core/Fixes/FixComparerExtensions.cs
index b817ada..6e4464c 100644
--- a/src/Nito.Comparers.Core/Fixes/FixComparerExtensions.cs
+++ b/src/Nito.Comparers.Core/Fixes/FixComparerExtensions.cs
@@ -23,7 +23,7 @@ public static IFullComparer WithStandardNullHandling(this IComparer? so
/// The type of objects being compared.
/// The source comparer. If this is null, the default comparer is used.
/// The GetHashCode implementation.
- public static IFullComparer WithGetHashCode(this IComparer? source, Func getHashCode) =>
+ public static IFullComparer WithGetHashCode(this IComparer? source, Func getHashCode) =>
new ExplicitGetHashCodeComparer(source, getHashCode);
///
diff --git a/src/Nito.Comparers.Core/Fixes/StandardNullHandlingComparer.cs b/src/Nito.Comparers.Core/Fixes/StandardNullHandlingComparer.cs
index 36bb96d..e229702 100644
--- a/src/Nito.Comparers.Core/Fixes/StandardNullHandlingComparer.cs
+++ b/src/Nito.Comparers.Core/Fixes/StandardNullHandlingComparer.cs
@@ -20,10 +20,10 @@ public StandardNullHandlingComparer(IComparer? source)
}
///
- protected override int DoCompare(T x, T y) => Source.Compare(x, y);
+ protected override int DoCompare(T? x, T? y) => Source.Compare(x!, y!);
///
- protected override int DoGetHashCode(T obj) => SourceGetHashCode(obj);
+ protected override int DoGetHashCode(T? obj) => SourceGetHashCode(obj);
///
/// Returns a short, human-readable description of the comparer. This is intended for debugging and not for other purposes.
diff --git a/src/Nito.Comparers.Core/Fixes/StandardNullHandlingEqualityComparer.cs b/src/Nito.Comparers.Core/Fixes/StandardNullHandlingEqualityComparer.cs
index 8101f81..6e8e7a0 100644
--- a/src/Nito.Comparers.Core/Fixes/StandardNullHandlingEqualityComparer.cs
+++ b/src/Nito.Comparers.Core/Fixes/StandardNullHandlingEqualityComparer.cs
@@ -21,10 +21,10 @@ public StandardNullHandlingEqualityComparer(IEqualityComparer? source)
}
///
- protected override int DoGetHashCode(T obj) => Source.GetHashCode(obj!);
+ protected override int DoGetHashCode(T? obj) => Source.GetHashCode(obj!);
///
- protected override bool DoEquals(T x, T y) => Source.Equals(x, y);
+ protected override bool DoEquals(T? x, T? y) => Source.Equals(x!, y!);
///
/// Returns a short, human-readable description of the comparer. This is intended for debugging and not for other purposes.
diff --git a/src/Nito.Comparers.Core/Internals/IStringSplitter.cs b/src/Nito.Comparers.Core/Internals/IStringSplitter.cs
new file mode 100644
index 0000000..181ee11
--- /dev/null
+++ b/src/Nito.Comparers.Core/Internals/IStringSplitter.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Nito.Comparers.Internals
+{
+ ///
+ ///
+ ///
+ public interface IStringSplitter
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ void MoveNext(string source, ref int offset, out int length, out bool isNumeric);
+ }
+}
diff --git a/src/Nito.Comparers.Core/Internals/ISubstringComparer.cs b/src/Nito.Comparers.Core/Internals/ISubstringComparer.cs
new file mode 100644
index 0000000..2627659
--- /dev/null
+++ b/src/Nito.Comparers.Core/Internals/ISubstringComparer.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Nito.Comparers.Internals
+{
+ public interface ISubstringComparer
+ {
+ int GetHashCode(string source, int offset, int length);
+ int Compare(string stringA, int offsetA, int lengthA, string stringB, int offsetB, int lengthB);
+ }
+}
diff --git a/src/Nito.Comparers.Core/Internals/Murmur3Hash.cs b/src/Nito.Comparers.Core/Internals/Murmur3Hash.cs
index 87f844b..626c9c9 100644
--- a/src/Nito.Comparers.Core/Internals/Murmur3Hash.cs
+++ b/src/Nito.Comparers.Core/Internals/Murmur3Hash.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
-#if !NET461 && !NETCOREAPP2_0 && !NETSTANDARD1_0 && !NETSTANDARD2_0 && !NETSTANDARD2_1
+#if !NET45 && !NET461 && !NETCOREAPP2_0 && !NETSTANDARD1_0 && !NETSTANDARD2_0 && !NETSTANDARD2_1
using static System.Numerics.BitOperations;
#endif
@@ -69,7 +69,7 @@ public void Combine(int data)
}
}
-#if NET461 || NETCOREAPP2_0 || NETSTANDARD1_0 || NETSTANDARD2_0 || NETSTANDARD2_1
+#if NET45 || NET461 || NETCOREAPP2_0 || NETSTANDARD1_0 || NETSTANDARD2_0 || NETSTANDARD2_1
private static uint RotateLeft(uint value, int bits) => (value << bits) | (value >> (32 - bits));
#endif
}
diff --git a/src/Nito.Comparers.Core/Internals/NaturalStringComparison.cs b/src/Nito.Comparers.Core/Internals/NaturalStringComparison.cs
new file mode 100644
index 0000000..cdbb993
--- /dev/null
+++ b/src/Nito.Comparers.Core/Internals/NaturalStringComparison.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+#pragma warning disable IDE0079
+#pragma warning disable IDE0057
+
+namespace Nito.Comparers.Internals
+{
+ ///
+ /// Implements "natural" string comparison.
+ ///
+ public static class NaturalStringComparison
+ {
+ private static readonly char[] Digits = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
+ private static Func OrdinalStringGetHashCode = GetSubstringGetHashCode(StringComparison.Ordinal);
+
+ ///
+ /// Gets a hash code for the specified string, using the specified string comparison for the text segments.
+ ///
+ /// The string to calculate the hash value of. May not be null.
+ /// The string delegate used to get the hash code of the text segments (not used for numeric segments).
+ public static int GetHashCode(string obj, Func substringGetHashCode)
+ {
+ _ = obj ?? throw new ArgumentNullException(nameof(obj));
+ _ = substringGetHashCode ?? throw new ArgumentNullException(nameof(substringGetHashCode));
+
+ var parser = new SegmentParser(obj);
+ var result = Murmur3Hash.Create();
+ while (!parser.IsDone)
+ {
+ parser.ParseNext();
+
+ // Note that leading zeros have been stripped from the range [start, end), so an ordinal comparison is sufficient to detect numeric equality.
+ var segmentGetHashCode = parser.IsNumeric ? OrdinalStringGetHashCode : substringGetHashCode;
+ var segmentHashCode = segmentGetHashCode(parser.Source, parser.Start, parser.Length);
+
+ result.Combine(segmentHashCode);
+ }
+
+ return result.HashCode;
+ }
+
+ ///
+ /// Returns a delegate that performs a substring hash code using the specified comparision type.
+ ///
+ /// The comparison type used by the returned delegate.
+ public static Func GetSubstringGetHashCode(StringComparison stringComparison)
+ {
+#if NETSTANDARD1_0 || NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NET45 || NET461
+ var comparer = TryGetComparer(stringComparison);
+ if (comparer == null)
+ return (str, offset, length) => 0;
+ return (str, offset, length) => comparer.GetHashCode(str.Substring(offset, length));
+#else
+ return (str, offset, length) => string.GetHashCode(str.AsSpan(offset, length), stringComparison);
+#endif
+
+ // Implementations, in order of preference:
+ // 1) Use string.GetHashCode (.NET Core 3.0+).
+ // 2) Forward to StringComparer.FromComparison (.NET Core 2.0-2.2, .NET Standard 2.1+).
+ // 3) Use a switch statement that supports all StringComparison values (.NET Framework 4.5+, .NET Standard 2.0).
+ // 4) Use a switch statement that doesn't support the invariant culture (.NET Standard 1.0-1.6).
+ // By platform:
+ // .NET Core 3.0+ - This method is not defined. string.GetHashCode is used instead.
+ // .NET Core 2.0-2.2 - This method forwards to StringComparer.FromComparison.
+ // .NET Framework 4.5+ - This method is a switch statement, supporting all StringComparison values.
+ // .NET Standard 2.1+ - This method forwards to StringComparer.FromComparison.
+ // .NET Standard 2.0 - This method is a switch statement, supporting all StringComparison values.
+ // .NET Standard 1.0-1.6 - This method is a switch statement and does not support invariant cultures.
+ // This can be a problem for Xamarin.Android 7.1, Xamarin.iOS 10.8, and Xamarin.Mac 3.0, all of which have invariant comparers but do not support .NET Standard 2.0.
+ // This will cause tons of hash code collisions.
+ // The recommended solution on those platforms is "upgrade to a .NET Standard 2.0-compatible version".
+#if NETSTANDARD1_0 || NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NET45 || NET461
+#if !NETSTANDARD1_0 && !NETSTANDARD2_0 && !NET45 && !NET461
+ static StringComparer? TryGetComparer(StringComparison comparison)
+ {
+ try
+ {
+ return StringComparer.FromComparison(comparison);
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ }
+#else
+ static StringComparer? TryGetComparer(StringComparison comparison)
+ {
+ return comparison switch
+ {
+ StringComparison.Ordinal => StringComparer.Ordinal,
+ StringComparison.OrdinalIgnoreCase => StringComparer.OrdinalIgnoreCase,
+ StringComparison.CurrentCulture => StringComparer.CurrentCulture,
+ StringComparison.CurrentCultureIgnoreCase => StringComparer.CurrentCultureIgnoreCase,
+#if !NETSTANDARD1_0
+ StringComparison.InvariantCulture => StringComparer.InvariantCulture,
+ StringComparison.InvariantCultureIgnoreCase => StringComparer.InvariantCultureIgnoreCase,
+#endif
+ _ => null,
+ };
+ }
+#endif
+#endif
+ }
+
+ ///
+ /// Compares the specified strings, using the specified string comparison for the text segments.
+ ///
+ /// The first string to compare. May not be null.
+ /// The first string to compare. May not be null.
+ /// The string delegate used to compare the text segments (not used for numeric segments).
+ public static int Compare(string x, string y, Func substringCompare)
+ {
+ _ = x ?? throw new ArgumentNullException(nameof(x));
+ _ = y ?? throw new ArgumentNullException(nameof(y));
+ _ = substringCompare ?? throw new ArgumentNullException(nameof(substringCompare));
+
+ var xParser = new SegmentParser(x);
+ var yParser = new SegmentParser(y);
+ while (!xParser.IsDone && !yParser.IsDone)
+ {
+ xParser.ParseNext();
+ yParser.ParseNext();
+ if (xParser.IsNumeric && yParser.IsNumeric)
+ {
+ var xLength = xParser.Length;
+ var yLength = yParser.Length;
+ if (xLength < yLength)
+ return -1;
+ else if (xLength > yLength)
+ return 1;
+ var compareResult = string.Compare(xParser.Source, xParser.Start, yParser.Source, yParser.Start, xLength, StringComparison.Ordinal);
+ if (compareResult != 0)
+ return compareResult;
+ }
+ else if (!xParser.IsNumeric && !yParser.IsNumeric)
+ {
+ var xLength = xParser.Length;
+ var yLength = yParser.Length;
+ var compareResult = substringCompare(xParser.Source, xParser.Start, xLength, yParser.Source, yParser.Start, yLength);
+ if (compareResult != 0)
+ return compareResult;
+ }
+ else if (xParser.IsNumeric)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+
+ if (xParser.IsDone && yParser.IsDone)
+ return 0;
+
+ if (xParser.IsDone)
+ return -1;
+ return 1;
+ }
+
+ ///
+ /// Returns a delegate that performs a substring comparison using the specified comparision type.
+ ///
+ /// The comparison type used by the returned delegate.
+ public static Func GetSubstringCompare(StringComparison stringComparison)
+ {
+ // Blatantly stolen from https://dogmamix.com/cms/blog/Finding-substrings
+ return stringComparison switch
+ {
+ StringComparison.CurrentCulture => (strA, indexA, lengthA, strB, indexB, lengthB) => CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None),
+ StringComparison.CurrentCultureIgnoreCase => (strA, indexA, lengthA, strB, indexB, lengthB) => CultureInfo.CurrentCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase),
+ (StringComparison)2 /* InvariantCulture */ => (strA, indexA, lengthA, strB, indexB, lengthB) => CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.None),
+ (StringComparison)3 /* InvariantCultureIgnoreCase */ => (strA, indexA, lengthA, strB, indexB, lengthB) => CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.IgnoreCase),
+ StringComparison.Ordinal => (strA, indexA, lengthA, strB, indexB, lengthB) => CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.Ordinal),
+ StringComparison.OrdinalIgnoreCase => (strA, indexA, lengthA, strB, indexB, lengthB) => CultureInfo.InvariantCulture.CompareInfo.Compare(strA, indexA, lengthA, strB, indexB, lengthB, CompareOptions.OrdinalIgnoreCase),
+ _ => throw new ArgumentException($"The string comparison type {stringComparison} is not supported.", nameof(stringComparison)),
+ };
+ }
+
+ private sealed class DefaultSubstringComparer : ISubstringComparer
+ {
+ private readonly Func _getCultureInfo;
+ private readonly CompareOptions _compareOptions;
+
+ public DefaultSubstringComparer(StringComparison comparison)
+ {
+ _getCultureInfo = comparison switch
+ {
+ StringComparison.CurrentCulture => () => CultureInfo.CurrentCulture,
+ StringComparison.CurrentCultureIgnoreCase => () => CultureInfo.CurrentCulture,
+ _ => () => CultureInfo.InvariantCulture,
+ };
+ }
+
+ public int Compare(string stringA, int inclusiveStartA, int exclusiveEndA, string stringB, int inclusiveStartB, int exclusiveEndB)
+ {
+ throw new NotImplementedException();
+ }
+
+ public int GetHashCode(string source, int inclusiveStart, int exclusiveEnd)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ private ref struct SegmentParser
+ {
+ public SegmentParser(string source)
+ {
+ Source = source;
+ Start = 0;
+ End = 0;
+ IsNumeric = false;
+ }
+
+ public string Source { get; }
+ public int Start { get; private set; }
+ public int End { get; private set; }
+ public int Length => End - Start;
+ public bool IsDone => End == Source.Length;
+ public bool IsNumeric { get; private set; }
+
+ public void ParseNext()
+ {
+ // Prerequisite: index < source.Length
+
+ Start = End;
+ var index = Start;
+ IsNumeric = IsDigit(Source[index++]);
+ if (IsNumeric)
+ {
+ // Skip leading zeros, but keep one if that's the only digit.
+ if (Source[Start] == '0')
+ {
+ do
+ {
+ ++Start;
+ } while (Start < Source.Length && Source[Start] == '0');
+ if (Start == Source.Length || !IsDigit(Source[Start]))
+ --Start;
+ index = Start + 1;
+ }
+
+ while (index < Source.Length && IsDigit(Source[index]))
+ ++index;
+ End = index;
+ }
+ else
+ {
+ index = Source.IndexOfAny(Digits, index);
+ if (index == -1)
+ index = Source.Length;
+ End = index;
+ }
+
+ static bool IsDigit(char ch) => ch >= '0' && ch <= '9';
+ }
+ }
+
+ private sealed class DefaultSplitter : IStringSplitter
+ {
+ public void MoveNext(string source, ref int offset, out int length, out bool isNumeric)
+ {
+ // Prerequisite: start < source.Length
+
+ var index = offset;
+ isNumeric = IsDigit(source[index++]);
+ if (isNumeric)
+ {
+ // Skip leading zeros, but keep one if that's the only digit.
+ if (source[offset] == '0')
+ {
+ do
+ {
+ ++offset;
+ } while (offset < source.Length && source[offset] == '0');
+ if (offset == source.Length || !IsDigit(source[offset]))
+ --offset;
+ index = offset + 1;
+ }
+
+ while (index < source.Length && IsDigit(source[index]))
+ ++index;
+ length = index - offset;
+ }
+ else
+ {
+ index = source.IndexOfAny(Digits, index);
+ if (index == -1)
+ index = source.Length;
+ length = index - offset;
+ }
+
+ static bool IsDigit(char ch) => ch >= '0' && ch <= '9';
+ }
+ }
+ }
+}
diff --git a/src/Nito.Comparers.Core/Nito.Comparers.Core.csproj b/src/Nito.Comparers.Core/Nito.Comparers.Core.csproj
index d8bf617..4523a18 100644
--- a/src/Nito.Comparers.Core/Nito.Comparers.Core.csproj
+++ b/src/Nito.Comparers.Core/Nito.Comparers.Core.csproj
@@ -1,7 +1,7 @@
The last comparison library you'll ever need!
- netstandard1.0;netstandard2.0;net461
+ netstandard1.0;netstandard2.0;netstandard2.1;net45;net461;netcoreapp2.0;netcoreapp3.0;netcoreapp5.0;net6.0
comparer;equalitycomparer;icomparable;iequatable
Nito.Comparers
diff --git a/src/Nito.Comparers.Core/Util/ComparableImplementations.cs b/src/Nito.Comparers.Core/Util/ComparableImplementations.cs
index 99f68e4..08be19c 100644
--- a/src/Nito.Comparers.Core/Util/ComparableImplementations.cs
+++ b/src/Nito.Comparers.Core/Util/ComparableImplementations.cs
@@ -15,10 +15,10 @@ public static class ComparableImplementations
/// The comparer. May not be null.
/// The object doing the implementing.
/// The other object.
- public static int ImplementCompareTo(IComparer comparer, T @this, T other) where T : IComparable
+ public static int ImplementCompareTo(IComparer comparer, T? @this, T? other) where T : IComparable
{
_ = comparer ?? throw new ArgumentNullException(nameof(comparer));
- return comparer.Compare(@this, other);
+ return comparer.Compare(@this!, other!);
}
///
@@ -27,7 +27,7 @@ public static int ImplementCompareTo(IComparer comparer, T @this, T other)
/// The comparer. May not be null.
/// The object doing the implementing.
/// The other object.
- public static int ImplementCompareTo(System.Collections.IComparer comparer, IComparable @this, object? obj)
+ public static int ImplementCompareTo(System.Collections.IComparer comparer, IComparable? @this, object? obj)
{
_ = comparer ?? throw new ArgumentNullException(nameof(comparer));
return comparer.Compare(@this, obj);
@@ -39,7 +39,7 @@ public static int ImplementCompareTo(System.Collections.IComparer comparer, ICom
/// The type of objects being compared.
/// The comparer. May not be null.
/// The object doing the implementing.
- public static int ImplementGetHashCode(IEqualityComparer equalityComparer, T @this)
+ public static int ImplementGetHashCode(IEqualityComparer equalityComparer, T? @this)
{
_ = equalityComparer ?? throw new ArgumentNullException(nameof(equalityComparer));
return equalityComparer.GetHashCode(@this!);
@@ -52,10 +52,10 @@ public static int ImplementGetHashCode(IEqualityComparer equalityComparer,
/// The comparer. May not be null.
/// The object doing the implementing.
/// The other object.
- public static bool ImplementEquals(IEqualityComparer equalityComparer, T @this, T other) where T : IEquatable
+ public static bool ImplementEquals(IEqualityComparer equalityComparer, T? @this, T? other) where T : IEquatable
{
_ = equalityComparer ?? throw new ArgumentNullException(nameof(equalityComparer));
- return equalityComparer.Equals(@this, other);
+ return equalityComparer.Equals(@this!, other!);
}
///
@@ -77,10 +77,10 @@ public static bool ImplementEquals(System.Collections.IEqualityComparer equality
/// The comparer. May not be null.
/// A value of type or null.
/// A value of type or null.
- public static bool ImplementOpEquality(IEqualityComparer equalityComparer, T left, T right)
+ public static bool ImplementOpEquality(IEqualityComparer equalityComparer, T? left, T? right)
{
_ = equalityComparer ?? throw new ArgumentNullException(nameof(equalityComparer));
- return equalityComparer.Equals(left, right);
+ return equalityComparer.Equals(left!, right!);
}
///
@@ -90,10 +90,10 @@ public static bool ImplementOpEquality(IEqualityComparer equalityComparer,
/// The comparer. May not be null.
/// A value of type or null.
/// A value of type or null.
- public static bool ImplementOpInequality(IEqualityComparer equalityComparer, T left, T right)
+ public static bool ImplementOpInequality(IEqualityComparer equalityComparer, T? left, T? right)
{
_ = equalityComparer ?? throw new ArgumentNullException(nameof(equalityComparer));
- return !equalityComparer.Equals(left, right);
+ return !equalityComparer.Equals(left!, right!);
}
///
@@ -103,10 +103,10 @@ public static bool ImplementOpInequality(IEqualityComparer equalityCompare
/// The comparer. May not be null.
/// A value of type or null.
/// A value of type or null.
- public static bool ImplementOpLessThan(IComparer comparer, T left, T right)
+ public static bool ImplementOpLessThan(IComparer comparer, T? left, T? right)
{
_ = comparer ?? throw new ArgumentNullException(nameof(comparer));
- return comparer.Compare(left, right) < 0;
+ return comparer.Compare(left!, right!) < 0;
}
///
@@ -116,10 +116,10 @@ public static bool ImplementOpLessThan(IComparer comparer, T left, T right
/// The comparer. May not be null.
/// A value of type or null.
/// A value of type or null.
- public static bool ImplementOpGreaterThan(IComparer comparer, T left, T right)
+ public static bool ImplementOpGreaterThan(IComparer comparer, T? left, T? right)
{
_ = comparer ?? throw new ArgumentNullException(nameof(comparer));
- return comparer.Compare(left, right) > 0;
+ return comparer.Compare(left!, right!) > 0;
}
///
@@ -129,10 +129,10 @@ public static bool ImplementOpGreaterThan(IComparer comparer, T left, T ri
/// The comparer. May not be null.
/// A value of type or null.
/// A value of type or null.
- public static bool ImplementOpLessThanOrEqual(IComparer comparer, T left, T right)
+ public static bool ImplementOpLessThanOrEqual(IComparer comparer, T? left, T? right)
{
_ = comparer ?? throw new ArgumentNullException(nameof(comparer));
- return comparer.Compare(left, right) <= 0;
+ return comparer.Compare(left!, right!) <= 0;
}
///
@@ -142,10 +142,10 @@ public static bool ImplementOpLessThanOrEqual(IComparer comparer, T left,
/// The comparer. May not be null.
/// A value of type or null.
/// A value of type or null.
- public static bool ImplementOpGreaterThanOrEqual(IComparer comparer, T left, T right)
+ public static bool ImplementOpGreaterThanOrEqual(IComparer comparer, T? left, T? right)
{
_ = comparer ?? throw new ArgumentNullException(nameof(comparer));
- return comparer.Compare(left, right) >= 0;
+ return comparer.Compare(left!, right!) >= 0;
}
}
}
diff --git a/src/Nito.Comparers.Core/Util/ComparerBase.cs b/src/Nito.Comparers.Core/Util/ComparerBase.cs
index f0fa003..464a25b 100644
--- a/src/Nito.Comparers.Core/Util/ComparerBase.cs
+++ b/src/Nito.Comparers.Core/Util/ComparerBase.cs
@@ -15,10 +15,10 @@ internal abstract class ComparerBase : EqualityComparerBase, IFullComparer
/// The first object to compare. May be null if is true.
/// The second object to compare. May be null if is true.
/// A value less than 0 if is less than , 0 if is equal to , or greater than 0 if is greater than .
- protected abstract int DoCompare(T x, T y);
+ protected abstract int DoCompare(T? x, T? y);
///
- protected override bool DoEquals(T x, T y) => Compare(x, y) == 0;
+ protected override bool DoEquals(T? x, T? y) => Compare(x, y) == 0;
///
/// Initializes a new instance of the class.
@@ -56,11 +56,11 @@ int System.Collections.IComparer.Compare(object? x, object? y)
}
}
- return DoCompare((T)x!, (T)y!);
+ return DoCompare((T?)x, (T?)y);
}
///
- public int Compare(T x, T y)
+ public int Compare(T? x, T? y)
{
if (!SpecialNullHandling)
{
@@ -76,7 +76,7 @@ public int Compare(T x, T y)
}
}
- return DoCompare(x!, y!);
+ return DoCompare(x, y);
}
}
}
diff --git a/src/Nito.Comparers.Core/Util/ComparerHelpers.cs b/src/Nito.Comparers.Core/Util/ComparerHelpers.cs
index dd3104b..931c2ce 100644
--- a/src/Nito.Comparers.Core/Util/ComparerHelpers.cs
+++ b/src/Nito.Comparers.Core/Util/ComparerHelpers.cs
@@ -16,10 +16,10 @@ internal static class ComparerHelpers
///
/// The type of objects being compared.
/// The comparer to use to calculate a hash code. May be null, but this method will throw an exception since null does not support hash codes.
- public static Func ComparerGetHashCode(IComparer? comparer)
+ public static Func ComparerGetHashCode(IComparer? comparer)
{
if (comparer is IEqualityComparer equalityComparer)
- return equalityComparer.GetHashCode;
+ return equalityComparer.GetHashCode!;
if (comparer is IEqualityComparer objectEqualityComparer)
return obj => objectEqualityComparer.GetHashCode(obj!);
diff --git a/src/Nito.Comparers.Core/Util/CompoundComparer.cs b/src/Nito.Comparers.Core/Util/CompoundComparer.cs
index 149bc50..2ca42a7 100644
--- a/src/Nito.Comparers.Core/Util/CompoundComparer.cs
+++ b/src/Nito.Comparers.Core/Util/CompoundComparer.cs
@@ -18,7 +18,7 @@ internal sealed class CompoundComparer : SourceComparerBase
///
/// The GetHashCode implementation for the second comparer.
///
- private readonly Func _secondSourceGetHashCode;
+ private readonly Func _secondSourceGetHashCode;
///
/// Initializes a new instance of the class.
@@ -33,7 +33,7 @@ public CompoundComparer(IComparer? source, IComparer? secondSource)
}
///
- protected override int DoGetHashCode(T obj)
+ protected override int DoGetHashCode(T? obj)
{
unchecked
{
@@ -44,12 +44,12 @@ protected override int DoGetHashCode(T obj)
}
///
- protected override int DoCompare(T x, T y)
+ protected override int DoCompare(T? x, T? y)
{
- var ret = Source.Compare(x, y);
+ var ret = Source.Compare(x!, y!);
if (ret != 0)
return ret;
- return _secondSource.Compare(x, y);
+ return _secondSource.Compare(x!, y!);
}
///
diff --git a/src/Nito.Comparers.Core/Util/CompoundEqualityComparer.cs b/src/Nito.Comparers.Core/Util/CompoundEqualityComparer.cs
index c982284..77a94ba 100644
--- a/src/Nito.Comparers.Core/Util/CompoundEqualityComparer.cs
+++ b/src/Nito.Comparers.Core/Util/CompoundEqualityComparer.cs
@@ -26,7 +26,7 @@ public CompoundEqualityComparer(IEqualityComparer? source, IEqualityComparer<
}
///
- protected override int DoGetHashCode(T obj)
+ protected override int DoGetHashCode(T? obj)
{
unchecked
{
@@ -37,12 +37,12 @@ protected override int DoGetHashCode(T obj)
}
///
- protected override bool DoEquals(T x, T y)
+ protected override bool DoEquals(T? x, T? y)
{
- var ret = Source.Equals(x, y);
+ var ret = Source.Equals(x!, y!);
if (!ret)
return false;
- return _secondSource.Equals(x, y);
+ return _secondSource.Equals(x!, y!);
}
///
diff --git a/src/Nito.Comparers.Core/Util/DefaultComparer.cs b/src/Nito.Comparers.Core/Util/DefaultComparer.cs
index ef69ba9..2288f35 100644
--- a/src/Nito.Comparers.Core/Util/DefaultComparer.cs
+++ b/src/Nito.Comparers.Core/Util/DefaultComparer.cs
@@ -19,13 +19,13 @@ static DefaultComparer()
}
///