2013-05-29 16 views
11

大我使用這個代碼來生成U+10FFFC使用Unicode字符超過2個字節與.net

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

我知道這是私人使用等,但它會顯示一個字符,我想預計何時顯示它。操縱這個unicode字符時會出現問題。

如果我後來做:

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

而不是將它打印只是單個字符,它打印兩個字符(即字符串顯然是由兩個字符)。如果我改變我的循環,這些字符添加回一個空字符串,像這樣:

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

在本月底,tmp將打印只是單個字符。

這裏究竟發生了什麼?我認爲char包含一個unicode字符,我從來不必擔心一個字符有多少個字節,除非我正在轉換爲字節。我真正的用例是我需要能夠檢測字符串中使用了非常大的unicode字符。目前我有這樣的事情:

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

但是,由於這種分裂非常大的字符,這是行不通的。我如何修改這個以使其工作?

回答

29

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不返回charchar是UTF-16代碼單元,而不是Unicode代碼點。爲了能夠返回所有Unicode代碼點,該方法不能返回一個char。有時它需要返回兩個,這就是爲什麼它使它成爲一個字符串。有時你會發現一些APIs在ints而不是char中處理,因爲int也可以用來處理所有的代碼點(這就是ConvertFromUtf32作爲參數所產生的結果,以及ConvertToUtf32產生的結果)。

string implements IEnumerable<char>,這意味着當您遍歷string時,每次迭代將得到一個UTF-16代碼單元。這就是爲什麼迭代你的字符串並打印出來會產生一些輸出有兩個「東西」的輸出。這些是構成U + 10FFFC表示的兩個UTF-16編碼單元。他們被稱爲「代理人」。第一個是高/領先代理人,第二個是低/代理人代理人。當你單獨打印它們時,它們不會產生有意義的輸出,因爲單獨的替代品在UTF-16中甚至不是有效的,並且它們也不被認爲是Unicode字符。

當您將追加這兩個代理人在環路的字符串,有效地重建代理對,並打印那雙後來作爲一個讓你正確的輸出。

而在咆哮的前端,請注意如何沒有任何抱怨說您在該循環中使用了畸形的UTF-16序列。它創建了一個單獨的替代項的字符串,但一切都進行,好像什麼也沒有發生:string類型甚至不是格式良好 UTF-16代碼單元序列,但類型任何 UTF-16代碼單元序列。

The char structure提供的靜態方法來處理代理人:IsHighSurrogateIsLowSurrogateIsSurrogatePairConvertToUtf32ConvertFromUtf32。如果你願意,你可以寫在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); 
} 
0

正如Martinho已經發布,這是很容易與本次非公開代碼點創建的字符串方式:

var s = char.ConvertFromUtf32(0x10FFFC); 

但通過該字符串的兩個char元素循環是毫無意義的:

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

用於什麼?您只需獲取編碼代碼點的高位和低位代理。記住char是一個16位的類型,所以它只能保持最大值0xFFFF。您的代碼點不適合16位類型,實際上對於最高代碼點,您需要21位(0x10FFFF),因此下一個更寬的類型只會是32位類型。兩個字符元素不是字符,而是代理對。 0x10FFFC的值被編碼到兩個代理中。

0

@R。 Martinho費爾南德斯的答案是正確的,他的AsCodePoints擴展方法有兩個問題:

  1. 這將拋出無效代碼百分點ArgumentException(高代理不低代理或反之亦然)。
  2. 如果您只有int代碼點,則不能使用char採用(char)(string, int)(例如char.IsNumber())的靜態方法。

我已經將代碼分成兩種方法,一種與原始代碼類似,但在無效的代碼點上返回Unicode Replacement Character。第二種方法返回的IEnumerable提供更多有用的字段一個struct:

StringCodePointExtensions.cs

public static class StringCodePointExtensions { 

    const char ReplacementCharacter = '\ufffd'; 

    public static IEnumerable<CodePointIndex> 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<int> 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

枚舉C#字符串中的UTF32字符的另一種替代方法是使用System.Globalization.StringInfo.GetTextElementEnumerator方法,如下面的代碼所示。

public static class StringExtensions 
{ 
    public static System.Collections.Generic.IEnumerable<UTF32Char> 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是要走的路。其餘代碼不正確。看看:https://msdn.microsoft.com/en-us/library/system.globalization.stringinfo(v=vs.110).aspx – X181

+0

這是不明確你的意思。這個答案的代碼有問題嗎? –

相關問題