SomeString.IndexOf(someString) возвращает 1 вместо 0 в .NET 4
Недавно мы обновили все наши проекты с .NET 3.5 до .NET 4. Я столкнулся с довольно странной проблемой в отношении string.IndexOf()
.
Мой код явно делает что-то немного другое, но в процессе изучения проблемы я обнаружил, что вызов IndexOf()
в строке с самим собой возвратил 1 вместо 0.
Другими словами:
string text = "\xAD\x2D"; // problem happens with "-dely N.China", too;
int index = text.IndexOf(text); // see update note below.
Дал мне индекс 1, а не 0. Несколько вещей, чтобы отметить эту проблему:
-
Проблемы, связанные с этими дефисами (первый символ - мягкий дефис Юникода, второй - обычный дефис).
-
Я проверил дважды, это не происходит в .NET 3.5, но в .NET 4.
-
Изменение IndexOf()
для сравнения по порядку устраняет проблему, поэтому по какой-то причине первый символ игнорируется по умолчанию IndexOf
.
Кто-нибудь знает, почему это происходит?
ИЗМЕНИТЬ
Извините, ребята, немного набросились на оригинальный пост и дважды нашли скрытую черту. Я обновил строку, это должно возвращать индекс 1 вместо 2, если вы вставляете его в правильный редактор.
Update:
Изменена исходная строка проблемы на ту, где каждый фактический символ хорошо виден (с помощью экранирования). Это немного упрощает вопрос.
Ответы
Ответ 1
Ваша строка существует из двух символов: мягкая дефис (код кода Unicode 173) и дефис (код кода Unicode 45).
Wiki: в соответствии со стандартом Unicode мягкая дефис не отображается, если линия не сломана в этой точке.
При использовании "\xAD\x2D".IndexOf("\xAD\x2D")
в .NET 4, кажется, игнорируется, что вы ищете мягкий дефис, возвращая начальный индекс 1 (индекс \x2D
). В .NET 3.5 это возвращает 0.
Больше удовольствия, если вы запустите этот код (поэтому, когда вы ищете мягкий дефис):
string text = "\xAD\x2D";
string shy = "\xAD";
int i1 = text.IndexOf(shy);
тогда i1
становится 0, независимо от используемой версии .NET. Результат text.IndexOf(text);
действительно меняется, что с первого взгляда выглядит как ошибка для меня.
Насколько я могу отследить структуру, более старые версии .NET используют InternalCall для IndexOfString()
(я не могу понять, к какому API), а из .NET 4 сделан QCall до InternalFindNLSStringEx()
, который, в свою очередь, вызывает FindNLSStringEx()
.
Проблема (я действительно не могу понять, является ли это предполагаемым поведением) действительно возникает при вызове FindNLSStringEx
:
LPCWSTR lpStringSource = L"\xAD\x2D";
LPCWSTR lpStringValue = L"\xAD";
int length;
int i = FindNLSStringEx(
LOCALE_NAME_SYSTEM_DEFAULT,
FIND_FROMSTART,
lpStringSource,
-1,
lpStringValue,
-1,
&length,
NULL,
NULL,
1);
Console::WriteLine(i);
i = FindNLSStringEx(
LOCALE_NAME_SYSTEM_DEFAULT,
FIND_FROMSTART,
lpStringSource,
-1,
lpStringSource,
-1,
&length,
NULL,
NULL,
1);
Console::WriteLine(i);
Console::ReadLine();
Печатает 0 и затем 1. Обратите внимание, что length
, параметр out, указывающий длину найденной строки, равен 0 после первого вызова и 1 после второго; мягкий дефис считается равным 0.
Обходной путь заключается в использовании text.IndexOf(text, StringComparison.OrdinalIgnoreCase);
, как вы уже отметили. Это делает QCall InternalCompareStringOrdinalIgnoreCase()
, который в свою очередь вызывает FindStringOrdinal()
, который возвращает 0 для обоих случаев.
Ответ 2
Кажется, это ошибка в .NET4, а новые изменения вернулись в .NET4 Beta1 к предыдущей версии, такой же, как .NET 2.0/3.0/3.5.
Что нового в BCL в .NET 4.0 CTP (блоги MSDN):
Изменения безопасности строк в .NET 4
По умолчанию частичные совпадающие перегрузки в System.String(StartsWith, EndsWith, IndexOf и LastIndexOf) по умолчанию были изменены как агностические (порядковые).
Это изменение повлияло на поведение метода String.IndexOf
, изменив их для выполнения сравнения по порядку (байта для байта) по умолчанию, будет изменено на использование CultureInfo.InvariantCulture
вместо CultureInfo.CurrentCulture
.
UPDATE для .NET 4 Beta 1
Чтобы поддерживать высокую совместимость между .NET 4 и предыдущими версиями, мы решили вернуть это изменение. Поведение String по умолчанию, частичное совпадение перегрузок и методы String и Char ToUpper и ToLower теперь ведут себя так же, как в .NET 2.0/3.0/3.5. Изменения в исходном поведении присутствуют в .NET 4 Beta 1.
Чтобы исправить это, измените метод сравнения строк на перегрузку, которая принимает перечисление System.StringComparison
в качестве параметра, и укажите либо Ordinal
, либо OrdinalIgnoreCase
.
// string contains 'unicode dash' \x2D
string text = "\xAD\x2D";
// woks in .NET 2.0/3.0/3.5 and .NET 4 Beta 1 and later
// but seems be buggy in .NET 4 because of 'culture-sensitive' comparison
int index = text.IndexOf(text);
// fixed version
index = text.IndexOf(text, StringComparison.Ordinal);
Ответ 3
Из документации (мой акцент):
Этот метод выполняет поиск слов (чувствительный к регистру и чувствительный к культуре) с использованием текущей культуры.
Т.е. некоторые отдельные кодовые точки будут считаться равными.
Что произойдет, если вы используете перегрузку, которая принимает значение StringComparison
и передает StringComparison.Ordinal
, чтобы избежать культурных зависимостей?