U+10FFFC — это одна кодовая точка Unicode, но интерфейс string
не предоставляет последовательность кодовых точек Unicode напрямую. Его интерфейс предоставляет последовательность кодовых единиц UTF-16. Это очень низкоуровневое представление текста. Очень жаль, что такое низкоуровневое представление текста было привито к самому очевидному и интуитивно понятному доступному интерфейсу... Я постараюсь не разглагольствовать о том, как мне не нравится этот дизайн, а просто скажу, что не имеет значения как жаль, это просто (печальный) факт, с которым вам приходится жить.
Во-первых, я предлагаю использовать char.ConvertFromUtf32
для получения исходной строки. Гораздо проще, намного читабельнее:
var s = char.ConvertFromUtf32(0x10FFFC);
Таким образом, Length
этой строки не равно 1, потому что, как я уже сказал, интерфейс работает с кодовыми единицами UTF-16, а не с кодовыми точками Unicode. U+10FFFC использует две кодовые единицы UTF-16, поэтому s.Length
равно 2. Все кодовые точки выше U+FFFF требуют для своего представления двух кодовых единиц UTF-16.
Обратите внимание, что ConvertFromUtf32
не возвращает char
: char
— это кодовая единица UTF-16, а не кодовая точка Unicode. Чтобы иметь возможность возвращать все кодовые точки Unicode, этот метод не может возвращать один char
. Иногда ему нужно вернуть два, и поэтому он делает его строкой. Иногда вы найдете некоторые API, работающие с int
s вместо char
, потому что int
также может использоваться для обработки всех кодовых точек (это то, что ConvertFromUtf32
принимает в качестве аргумента, и что ConvertToUtf32
создает в качестве результата).
string
реализует IEnumerable<char>
, что означает, что при повторении string
вы получаете одну единицу кода UTF-16 за итерацию. Вот почему повторение вашей строки и ее распечатка дает неверный вывод с двумя «вещами» в нем. Это две кодовые единицы UTF-16, составляющие представление U+10FFFC. Их называют «суррогатами». Первый - это суррогат с высоким / ведущим, а второй - суррогат с низким / следом. Когда вы печатаете их по отдельности, они не производят значимого вывода, потому что одинокие суррогаты даже не допустимы в UTF-16, и они также не считаются символами Unicode.
Когда вы добавляете эти два суррогата к строке в цикле, вы фактически восстанавливаете суррогатную пару, а печать этой пары позже как одного дает правильный результат.
И в начале разглагольствований обратите внимание, что ничто не жалуется на то, что вы использовали искаженную последовательность UTF-16 в этом цикле. Он создает строку с единственным суррогатом, но все продолжается как ни в чем не бывало: тип string
— это даже не тип правильных последовательностей кодовых единиц UTF-16, а тип < strong>любая последовательность кодовых единиц UTF-16.
Структура char
предоставляет статические методы для работы с суррогатами: IsHighSurrogate
, IsLowSurrogate
, IsSurrogatePair
, ConvertToUtf32
и ConvertFromUtf32
. Если вы хотите, вы можете написать итератор, который перебирает символы Unicode вместо единиц кода UTF-16:
static IEnumerable<int> AsCodePoints(this string s)
{
for(int i = 0; i < s.Length; ++i)
{
yield return char.ConvertToUtf32(s, i);
if(char.IsHighSurrogate(s, i))
i++;
}
}
Затем вы можете повторять как:
foreach(int codePoint in s.AsCodePoints())
{
// do stuff. codePoint will be an int will value 0x10FFFC in your example
}
Если вы предпочитаете получать каждую кодовую точку в виде строки, вместо этого измените тип возвращаемого значения на IEnumerable<string>
, а строку доходности на:
yield return char.ConvertFromUtf32(char.ConvertToUtf32(s, i));
В этой версии следующее работает как есть:
foreach(string codePoint in s.AsCodePoints())
{
Console.WriteLine(codePoint);
}
person
R. Martinho Fernandes
schedule
29.05.2013