Подтвердить что ты не робот

String.StartsWith не работает с азиатскими языками?

Я заметил эту странную проблему. Проверьте это вьетнамский (согласно Google Translate) строка:

string line = "Mìng-dĕ̤ng-ngṳ̄";
string sub = "Mìng-dĕ̤ng-ngṳ";
line.Length
15
sub.Length
14
line.StartsWith(sub)
false

Мне кажется, что результат неправильный. Итак, я выполнил свою собственную функцию StartWith, которая сравнивает строку char -by- char.

public bool CustomStartWith(string parent, string child)
{
    for (int i = 0; i < child.Length; i++)
    {
        if (parent[i] != child[i])
            return false;
    }
    return true;
}

И, как я думаю, результаты выполнения этой функции

CustomStartWith("Mìng-dĕ̤ng-ngṳ̄", "Mìng-dĕ̤ng-ngṳ")
true

Что здесь происходит?! Как это возможно?

4b9b3361

Ответ 1

Результат, возвращаемый StartsWith, верен. По умолчанию большинство методов сравнения строк выполняют культурно-чувствительные сравнения, используя текущую культуру, а не обычные байтовые последовательности. Хотя ваш line начинается с последовательности байтов, идентичной sub, подстрока, которую она представляет, не эквивалентна в большинстве (или всех) культурах.

Если вам действительно нужно сравнение, которое обрабатывает строки как обычные байтовые последовательности, используйте перегрузку:

line.StartsWith(sub, StringComparison.Ordinal);                       // true

Если вы хотите, чтобы сравнение было нечувствительным к регистру:

line.StartsWith(sub, StringComparison.OrdinalIgnoreCase);             // true

Вот более знакомый пример:

var line1 = "café";   // 63 61 66 E9     – precomposed character 'é' (U+00E9)
var line2 = "café";   // 63 61 66 65 301 – base letter e (U+0065) and
                      //                   combining acute accent (U+0301)
var sub   = "cafe";   // 63 61 66 65 
Console.WriteLine(line1.StartsWith(sub));                             // false
Console.WriteLine(line2.StartsWith(sub));                             // false
Console.WriteLine(line1.StartsWith(sub, StringComparison.Ordinal));   // false
Console.WriteLine(line2.StartsWith(sub, StringComparison.Ordinal));   // true

В приведенных выше примерах line2 начинается с той же последовательности байтов, что и sub, за которой следует комбинационный острый акцент (U + 0301), который должен быть применен к окончательному e. line1 использует прекомпонованный символ для é (U + 00E9), поэтому его последовательность байтов не соответствует значению sub.

В реальной семантике обычно не следует считать cafe подстрокой café; e и рассматриваются как разные символы. То, что представляется в виде пары символов, начиная с e, является внутренней деталью реализации схемы кодирования (Unicode), которая не должна влиять на результаты. Это демонстрируется вышеприведенным примером, противопоставляющим café и café; не ожидалось бы разного результата, если конкретно не было запланировано сравнение по порядку (побайтовое).

Адаптация этого объяснения к вашему примеру:

string line = "Mìng-dĕ̤ng-ngṳ̄";   // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73 304
string sub  = "Mìng-dĕ̤ng-ngṳ";   // 4D EC 6E 67 2D 64 115 324 6E 67 2D 6E 67 1E73

Каждый символ .NET представляет собой код кода UTF-16, значения которого показаны в комментариях выше. Первые 14 блоков кода идентичны, поэтому сравнение char -by- char равно true (точно так же, как StringComparison.Ordinal). Тем не менее, 15-й блок кода в line является объединяющим макроном, ◌̄ (U + 0304), который сочетается с предыдущим (U + 1E73), чтобы дать ṳ̄.

Ответ 2

Это не ошибка. String.StartsWith на самом деле намного умнее, чем просто пошаговая проверка ваших двух строк. Он учитывает вашу текущую культуру (языковые настройки и т.д.) И учитывает сокращения и специальные символы. (Вам все равно, что вам нужно два символа, чтобы в итоге получилось ṳ̄. Он сравнивает его как один).

Таким образом, это означает, что если вы не хотите принимать все эти специфические настройки для культуры и просто хотите проверить его с помощью порядкового сравнения, вы должны сообщить этому компаратору.

Это правильный способ сделать это (не игнорируя случай, например Douglas!):

line.StartsWith(sub, StringComparison.Ordinal);