Выкарыстанне знакаў Юнікода больш, чым 2 байта з .Net

I'm using this code to generate U+10FFFC

var s = Encoding.UTF8.GetString(new byte[] {0xF4,0x8F,0xBF,0xBC});

Я ведаю, што для прыватнага выкарыстання і такога, але гэта сапраўды паказвае адзін знак, як я б чакаць пры адлюстраванні яго. Праблемы ўзнікаюць пры маніпуляванні гэтага юнікода характару.

Калі я пазней зрабіць гэта:

foreach(var ch in s)
{
    Console.WriteLine(ch);
}

Замест таго, каб гэта друк толькі аднаго знака, ён друкуе два знака (гэта значыць радок, па-відаць, складаецца з двух знакаў). Калі б я змяніць свой цыкл, каб дадаць гэтыя сімвалы назад у пусты радок, напрыклад, так:

string tmp="";
foreach(var ch in s)
{
    Console.WriteLine(ch);
    tmp += ch;
}

У канцы гэтага TMP будзе друкаваць толькі адзін знак.

Што менавіта тут адбываецца? Я думаў, што сімвал змяшчае адзін юнікода характар, і я ніколі не даводзілася турбавацца аб тым, колькі байтаў персанаж, калі я не раблю пераўтварэнне ў байтах. Мой рэальны выпадак выкарыстання я павінен быць у стане выявіць, калі вельмі вялікія знакі Юнікода выкарыстоўваецца ў радку. У цяперашні час у мяне ёсць нешта накшталт гэтага:

foreach(var ch in s)
{
    if(ch>=0x100000 && ch<=0x10FFFF)
    {
        Console.WriteLine("special character!");
    }
}

Аднак, з-за гэта расшчапленне вельмі вялікіх сімвалаў, гэта не працуе. Як я магу змяніць гэта, каб прымусіць яго працаваць?

11

7 адказы

U + 10FFFC адна кропка кода Unicode, але інтэрфейс Радок 'ы не раскрывае паслядоўнасць кодавых кропак Unicode напрамую. Яго інтэрфейс прадастаўляе паслядоўнасць UTF-16 адзінак коды. Гэта вельмі выгляд нізкаўзроўневага тэксту. Вельмі сумна, што такі від нізкаўзроўневага тэксту быў прышчэплены на самы відавочны і інтуітыўна зразумелы інтэрфейс, даступны ... Я паспрабую не размаўляць шмат пра тое, як мне не падабаецца гэты дызайн, і проста сказаць, што не мае значэння як няўдала, гэта проста (сумна) факт, што вы павінны жыць.

Па-першае, я прапаную выкарыстоўваць char.ConvertFromUtf32 </а > каб атрымаць зыходную радок. Нашмат прасцей, значна больш зручным для чытання:

var s = char.ConvertFromUtf32(0x10FFFC);

Такім чынам, гэты радок у Даўжыня ня 1, таму што, як я ўжо сказаў, справа інтэрфейсу ў UTF-16 адзінак кода, а не Unicode код кропак. U + 10FFFC выкарыстоўвае два UTF-16 кодавых адзінак, таму s.length роўны 2. Усе кодавыя пункту вышэй U + FFFF патрабуецца два UTF-16 кодавых блокаў для іх прадстаўлення.

Варта адзначыць, што ConvertFromUtf32 не вяртае сімвал : сімвал гэта код блока UTF-16, а не Unicode код пункта. Для таго, каб мець магчымасць вярнуць усе кодавыя пункту Unicode, што метад не можа вяртаць адзін сімвал . Часам трэба вярнуць два, і вось чаму ён робіць гэта радок. Часам вы знойдзеце некаторыя API, якія займаюцца Int S замест сімвал , таму што Int можа быць выкарыстаны для апрацоўкі ўсіх кропак кода таксама (вось што <�код > ConvertFromUtf32 прымае ў якасці аргументу, і што ConvertToUtf32 вырабляе як вынік).

string implements IEnumerable, which means that when you iterate over a string you get one UTF-16 code unit per iteration. That's why iterating your string and printing it out yields some broken output with two "things" in it. Those are the two UTF-16 code units that make up the representation of U+10FFFC. They are called "surrogates". The first one is a high/lead surrogate and the second one is a low/trail surrogate. When you print them individually they do not produce meaningful output because lone surrogates are not even valid in UTF-16, and they are not considered Unicode characters either.

Калі вы дадаеце гэтыя двух сурагатаў ў радок у цыкле, вы эфектыўна аднавіць сурагатнай пару, і друк гэтага пары пазней як адзін атрымлівае вам правільны выхад.

І ў размовы фронце, звярніце ўвагу, як нічога не скардзіцца, што вы выкарыстоўвалі некарэктны UTF-16 паслядоўнасць у гэтым цыкле. Ён стварае радок з адзінокім сурагату, і тым не менш усё працягвае, як быццам нічога не адбылося: функцыя радок тып не нават тып <�моцны> добра сфармаваны UTF-16 код блока паслядоўнасці, але тып <�моцны> любы </моцнай> адзінкавай паслядоўнасці UTF-16 кода.

The char structure provides static methods to deal with surrogates: IsHighSurrogate, IsLowSurrogate, IsSurrogatePair, ConvertToUtf32, and ConvertFromUtf32. If you want you can write an iterator that iterates over Unicode characters instead of UTF-16 code units:

static IEnumerable 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
}

If you prefer to get each code point as a string instead change the return type to IEnumerable and the yield line to:

yield return char.ConvertFromUtf32(char.ConvertToUtf32(s, i));

З гэтай версіяй, наступныя работы, як ёсць:

foreach(string codePoint in s.AsCodePoints())
{
     Console.WriteLine(codePoint);
}
33
дададзена

Яшчэ адна альтэрнатыва для пералічэння сімвалаў UTF32 ў З # радок з'яўляецца выкарыстанне System.Globalization.StringInfo.GetTextElementEnumerator метад, як у прыведзеным ніжэй кодзе.

public static class StringExtensions
{
    public static System.Collections.Generic.IEnumerable GetUTF32Chars(this string s)
    {
        var tee = System.Globalization.StringInfo.GetTextElementEnumerator(s);

        while (tee.MoveNext())
        {
            yield return new UTF32Char(s, tee.ElementIndex);
        }
    }
}

public struct UTF32Char
{
    private string s;
    private int index;

    public UTF32Char(string s, int index)
    {
        this.s = s;
        this.index = index;
    }

    public override string ToString()
    {
        return char.ConvertFromUtf32(this.UTF32Code);
    }

    public int UTF32Code {  get { return char.ConvertToUtf32(s, index); } }
    public double NumericValue { get { return char.GetNumericValue(s, index); } }
    public UnicodeCategory UnicodeCategory { get { return char.GetUnicodeCategory(s, index); } } 
    public bool IsControl { get { return char.IsControl(s, index); } }
    public bool IsDigit { get { return char.IsDigit(s, index); } }
    public bool IsLetter { get { return char.IsLetter(s, index); } }
    public bool IsLetterOrDigit { get { return char.IsLetterOrDigit(s, index); } }
    public bool IsLower { get { return char.IsLower(s, index); } }
    public bool IsNumber { get { return char.IsNumber(s, index); } }
    public bool IsPunctuation { get { return char.IsPunctuation(s, index); } }
    public bool IsSeparator { get { return char.IsSeparator(s, index); } }
    public bool IsSurrogatePair { get { return char.IsSurrogatePair(s, index); } }
    public bool IsSymbol { get { return char.IsSymbol(s, index); } }
    public bool IsUpper { get { return char.IsUpper(s, index); } }
    public bool IsWhiteSpace { get { return char.IsWhiteSpace(s, index); } }
}
0
дададзена
System.Globalization.StringInfo гэта шлях. Астатняя частка кода не з'яўляецца правільным. Паглядзіце: MSDN. microsoft.com/en-us/library/…
дададзена аўтар X181, крыніца
Не ясна, што вы маеце на ўвазе. Ці ёсць праблема з кодам з гэтага адказу?
дададзена аўтар Andrei Bozantan, крыніца

Яшчэ адна альтэрнатыва для пералічэння сімвалаў UTF32 ў З # радок з'яўляецца выкарыстанне System.Globalization.StringInfo.GetTextElementEnumerator метад, як у прыведзеным ніжэй кодзе.

public static class StringExtensions
{
    public static System.Collections.Generic.IEnumerable GetUTF32Chars(this string s)
    {
        var tee = System.Globalization.StringInfo.GetTextElementEnumerator(s);

        while (tee.MoveNext())
        {
            yield return new UTF32Char(s, tee.ElementIndex);
        }
    }
}

public struct UTF32Char
{
    private string s;
    private int index;

    public UTF32Char(string s, int index)
    {
        this.s = s;
        this.index = index;
    }

    public override string ToString()
    {
        return char.ConvertFromUtf32(this.UTF32Code);
    }

    public int UTF32Code {  get { return char.ConvertToUtf32(s, index); } }
    public double NumericValue { get { return char.GetNumericValue(s, index); } }
    public UnicodeCategory UnicodeCategory { get { return char.GetUnicodeCategory(s, index); } } 
    public bool IsControl { get { return char.IsControl(s, index); } }
    public bool IsDigit { get { return char.IsDigit(s, index); } }
    public bool IsLetter { get { return char.IsLetter(s, index); } }
    public bool IsLetterOrDigit { get { return char.IsLetterOrDigit(s, index); } }
    public bool IsLower { get { return char.IsLower(s, index); } }
    public bool IsNumber { get { return char.IsNumber(s, index); } }
    public bool IsPunctuation { get { return char.IsPunctuation(s, index); } }
    public bool IsSeparator { get { return char.IsSeparator(s, index); } }
    public bool IsSurrogatePair { get { return char.IsSurrogatePair(s, index); } }
    public bool IsSymbol { get { return char.IsSymbol(s, index); } }
    public bool IsUpper { get { return char.IsUpper(s, index); } }
    public bool IsWhiteSpace { get { return char.IsWhiteSpace(s, index); } }
}
0
дададзена
System.Globalization.StringInfo гэта шлях. Астатняя частка кода не з'яўляецца правільным. Паглядзіце: MSDN. microsoft.com/en-us/library/…
дададзена аўтар X181, крыніца
Не ясна, што вы маеце на ўвазе. Ці ёсць праблема з кодам з гэтага адказу?
дададзена аўтар Andrei Bozantan, крыніца

У той час як @R. Адказ Martinho Fernandes з'яўляецца правільным, яго AsCodePoints метад пашырэння мае два пытанні:

  1. Гэта будзе згенераваны ArgumentException на несапраўдным кодавым (высокі сурагат без нізкага сурагату ці наадварот).
  2. Вы не можаце выкарыстоўваць сімвал статычныя метады, якія прымаюць (знак) або (радок, INT) (напрыклад, паўкокс .IsNumber() ), калі ў вас ёсць толькі кропкі INT кода.

Я падзяліць код на два метаду, адзін падобны на арыгінал, але вяртае Замена Unicode характар ​​ на несапраўдных кропках коды. Другі метад вяртае IEnumerable-структуру з больш карыснымі палёў:

<�Код> StringCodePointExtensions.cs

public static class StringCodePointExtensions {

    const char ReplacementCharacter = '\ufffd';

    public static IEnumerable CodePointIndexes(this string s) {
        for (int i = 0; i < s.Length; i++) {
            if (char.IsHighSurrogate(s, i)) {
                if (i + 1 < s.Length && char.IsLowSurrogate(s, i + 1)) {
                    yield return CodePointIndex.Create(i, true, true);
                    i++;
                    continue;

                } else {
                   //High surrogate without low surrogate
                    yield return CodePointIndex.Create(i, false, false);
                    continue;
                }

            } else if (char.IsLowSurrogate(s, i)) {
               //Low surrogate without high surrogate
                yield return CodePointIndex.Create(i, false, false);
                continue;
            }

            yield return CodePointIndex.Create(i, true, false);
        }
    }

    public static IEnumerable CodePointInts(this string s) {
        return s
            .CodePointIndexes()
            .Select(
            cpi => {
                if (cpi.Valid) {
                    return char.ConvertToUtf32(s, cpi.Index);
                } else {
                    return (int)ReplacementCharacter;
                }
            });
    }
}

CodePointIndex.cs:

public struct CodePointIndex {
    public int Index;
    public bool Valid;
    public bool IsSurrogatePair;

    public static CodePointIndex Create(int index, bool valid, bool isSurrogatePair) {
        return new CodePointIndex {
            Index = index,
            Valid = valid,
            IsSurrogatePair = isSurrogatePair,
        };
    }
}

CC0

<�Суб>, наколькі гэта магчыма ў адпаведнасці з законам, асоба, звязаны CC0 з гэтай працай адмовіўся ад усіх аўтарскіх правоў і звязаных з імі або сумежных правоў на гэтую працу. </Суб>

0
дададзена

У той час як @R. Адказ Martinho Fernandes з'яўляецца правільным, яго AsCodePoints метад пашырэння мае два пытанні:

  1. Гэта будзе згенераваны ArgumentException на несапраўдным кодавым (высокі сурагат без нізкага сурагату ці наадварот).
  2. Вы не можаце выкарыстоўваць сімвал статычныя метады, якія прымаюць (знак) або (радок, INT) (напрыклад, паўкокс .IsNumber() ), калі ў вас ёсць толькі кропкі INT кода.

Я падзяліць код на два метаду, адзін падобны на арыгінал, але вяртае Замена Unicode характар ​​ на несапраўдных кропках коды. Другі метад вяртае IEnumerable-структуру з больш карыснымі палёў:

<�Код> StringCodePointExtensions.cs

public static class StringCodePointExtensions {

    const char ReplacementCharacter = '\ufffd';

    public static IEnumerable CodePointIndexes(this string s) {
        for (int i = 0; i < s.Length; i++) {
            if (char.IsHighSurrogate(s, i)) {
                if (i + 1 < s.Length && char.IsLowSurrogate(s, i + 1)) {
                    yield return CodePointIndex.Create(i, true, true);
                    i++;
                    continue;

                } else {
                   //High surrogate without low surrogate
                    yield return CodePointIndex.Create(i, false, false);
                    continue;
                }

            } else if (char.IsLowSurrogate(s, i)) {
               //Low surrogate without high surrogate
                yield return CodePointIndex.Create(i, false, false);
                continue;
            }

            yield return CodePointIndex.Create(i, true, false);
        }
    }

    public static IEnumerable CodePointInts(this string s) {
        return s
            .CodePointIndexes()
            .Select(
            cpi => {
                if (cpi.Valid) {
                    return char.ConvertToUtf32(s, cpi.Index);
                } else {
                    return (int)ReplacementCharacter;
                }
            });
    }
}

CodePointIndex.cs:

public struct CodePointIndex {
    public int Index;
    public bool Valid;
    public bool IsSurrogatePair;

    public static CodePointIndex Create(int index, bool valid, bool isSurrogatePair) {
        return new CodePointIndex {
            Index = index,
            Valid = valid,
            IsSurrogatePair = isSurrogatePair,
        };
    }
}

CC0

<�Суб>, наколькі гэта магчыма ў адпаведнасці з законам, асоба, звязаны CC0 з гэтай працай адмовіўся ад усіх аўтарскіх правоў і звязаных з імі або сумежных правоў на гэтую працу. </Суб>

0
дададзена

Як пісаў ужо да Марціна, значна прасцей стварыць радок з гэтым прыватным чынам, што элемент кода:

var s = char.ConvertFromUtf32(0x10FFFC);

Але ў цыкле праз два знакавых элементаў гэтага радка не мае сэнсу:

foreach(var ch in s)
{
    Console.WriteLine(ch);
}

Навошта? Вы проста атрымаць высокі і нізкі сурагат, якія кадуюць элемент кода. Памятаеце сімвал ўяўляе сабой тып 16 біта, таму ён можа трымаць толькі максімальнае значэнне 0xFFFF. Ваш элемент кода не ўпісваецца ў тып 16 біта, на самай справе для самага высокага элемента кода вам патрэбныя 21 біт (0x10FFFF), так што наступны шырэй тып будзе проста тыпам 32 біт. Два знакавыя элементы не з'яўляюцца сімваламі, але сурагатная пара. Значэнне 0x10FFFC кадуецца ў двух сурагатаў.

0
дададзена

Як пісаў ужо да Марціна, значна прасцей стварыць радок з гэтым прыватным чынам, што элемент кода:

var s = char.ConvertFromUtf32(0x10FFFC);

Але ў цыкле праз два знакавых элементаў гэтага радка не мае сэнсу:

foreach(var ch in s)
{
    Console.WriteLine(ch);
}

Навошта? Вы проста атрымаць высокі і нізкі сурагат, якія кадуюць элемент кода. Памятаеце сімвал ўяўляе сабой тып 16 біта, таму ён можа трымаць толькі максімальнае значэнне 0xFFFF. Ваш элемент кода не ўпісваецца ў тып 16 біта, на самай справе для самага высокага элемента кода вам патрэбныя 21 біт (0x10FFFF), так што наступны шырэй тып будзе проста тыпам 32 біт. Два знакавыя элементы не з'яўляюцца сімваламі, але сурагатная пара. Значэнне 0x10FFFC кадуецца ў двух сурагатаў.

0
дададзена