Хеш-код строки не работает в .NET Core 2.1, но работает в 2.0

Недавно я обновил один из своих проектов с .NET Core 2.0 до .NET Core 2.1. После этого несколько моих тестов начали проваливаться.

После сужения этого я обнаружил, что в .NET Core 2.1 невозможно вычислить хеш-код строки с использованием компаратора, учитывающего культуру, с опцией сравнения сортировки строк.

Я создал тест, который воспроизводит мою проблему:

[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!
}

Я проверил это на нескольких платформах со следующими результатами:

  • .NET Core 2.0: ✔ ПРОЙДИТЕ
  • .NET Core 2.1: ✖ FAIL
  • .NET Framework 4.7: ✖ FAIL
  • .NET Framework 4.6.2: ✖ FAIL

При CompareInfo.GetHashCodeOfString ArgumentException CompareInfo.GetHashCodeOfString из CompareInfo.GetHashCodeOfString говоря:

Значение флагов неверно

Теперь на мои вопросы:

  1. Почему нельзя использовать CompareOptions.StringSort при вычислении хеш-кода?

  2. Почему это было разрешено в .NET Core 2.0?

Насколько я понимаю, CompareOptions.StringSort влияет только на относительный порядок сортировки строк и не должен влиять на вычисление хеш-кода. MSDN говорит:

StringSort Указывает, что для сравнения строк необходимо использовать алгоритм сортировки строк. При сортировке строк дефис и апостроф, а также другие не буквенно-цифровые символы располагаются перед буквенно-цифровыми символами.

Ответы

Ответ 1

Команда corefx подтвердила, что это ошибка в .NET Core 2.1, а также в полной версии .NET Framework с 4. 6+.

Они также признают, что будет трудно изменить это поведение в полной среде, и поэтому могут рассмотреть вопрос о том, чтобы сохранить поведение как есть в .NET Core 2. 1+ для обеспечения согласованности между .NET Core и полной структурой.

Возможный обходной путь - использовать такой класс:

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);
}