I recently upgraded one of my projects from .NET Core 2.0 to .NET Core 2.1. After doing so several of my tests started to fail.
After narrowing this down I've found that in .NET Core 2.1 it is not possible to compute the hash code of a string using a culture aware comparer with the string sort compare option.
I've created a test that reproduce my problem:
[TestMethod]
public void Can_compute_hash_code_using_invariant_string_sort_comparer()
{
var compareInfo = CultureInfo.InvariantCulture.CompareInfo;
var stringComparer = compareInfo.GetStringComparer(CompareOptions.StringSort);
stringComparer.GetHashCode("test"); // should not throw!
}
I've tested it on a couple of frameworks with the following results:
When failing an ArgumentException
is thrown from CompareInfo.GetHashCodeOfString
saying:
Value of flags is invalid
Now, to my questions:
Why is it not allowed to use CompareOptions.StringSort
when computing a hash code?
Why was it allowed in .NET Core 2.0?`
As far as I understand CompareOptions.StringSort
only affects the relative sort order of strings and should not affect hash code computation. MSDN says:
StringSort Indicates that the string comparison must use the string sort algorithm. In a string sort, the hyphen and the apostrophe, as well as other nonalphanumeric symbols, come before alphanumeric characters.
The corefx team has confirmed that this is a bug in .NET Core 2.1 and also in the full .NET Framework as of 4.6+.
They also acknowledge that it will be hard to change this behavior in the full framework and may therefore consider to keep the behavior as-is in .NET Core 2.1+ to maintain consistency between .NET Core and the full framework.
A possible workaround is to use a class like this:
internal sealed class CultureAwareStringSortComparer : StringComparer
{
public CultureAwareStringSortComparer(
CompareInfo compareInfo,
CompareOptions options = CompareOptions.StringSort)
{
Requires.ArgNotNull(compareInfo, nameof(compareInfo));
this.SortComparer = compareInfo.GetStringComparer(options);
this.HashCodeComparer = compareInfo.GetStringComparer(
options & ~CompareOptions.StringSort);
}
internal StringComparer SortComparer { get; }
internal StringComparer HashCodeComparer { get; }
public override int Compare(string x, string y) => this.SortComparer.Compare(x, y);
public override bool Equals(string x, string y) => this.SortComparer.Equals(x, y);
public override int GetHashCode(string obj) => this.HashCodeComparer.GetHashCode(obj);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With