2013-05-29 24 views
11

Tôi đang sử dụng mã này để tạo ra U+10FFFCSử dụng các ký tự unicode lớn hơn 2 byte với Net

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

Tôi biết đó là cho sử dụng cá nhân và như vậy, nhưng nó không hiển thị một ký tự đơn như tôi muốn mong đợi khi hiển thị nó. Các vấn đề xảy ra khi thao tác với ký tự unicode này.

Nếu sau này tôi làm điều này:

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

Thay vì nó in chỉ là nhân vật duy nhất, nó in hai ký tự (ví dụ: chuỗi được rõ ràng gồm hai ký tự). Nếu tôi thay đổi vòng lặp của tôi để thêm những nhân vật này trở về một chuỗi rỗng như vậy:

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

Vào cuối này, tmp sẽ in chỉ là một nhân vật duy nhất.

Chính xác những gì đang xảy ra ở đây? Tôi nghĩ rằng char chứa một ký tự unicode duy nhất và tôi không bao giờ phải lo lắng về bao nhiêu byte một ký tự là trừ khi tôi đang làm chuyển đổi sang byte. Trường hợp sử dụng thực sự của tôi là tôi cần để có thể phát hiện khi các ký tự unicode rất lớn được sử dụng trong một chuỗi. Hiện tại tôi có nội dung như sau:

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

Tuy nhiên, do việc tách các ký tự rất lớn, điều này không hiệu quả. Làm thế nào tôi có thể sửa đổi điều này để làm cho nó hoạt động?

Trả lời

29

U + 10FFFC là một điểm mã Unicode, nhưng giao diện của string không hiển thị một chuỗi mã Unicode trực tiếp. Giao diện của nó cho thấy một chuỗi các đơn vị mã UTF-16. Đó là chế độ xem văn bản rất thấp. Thật không may là một cái nhìn mức độ thấp của văn bản được ghép vào giao diện rõ ràng và trực quan nhất có sẵn ... Tôi sẽ cố gắng không rant nhiều về cách tôi không thích thiết kế này, và chỉ nói rằng không quan trọng thật không may, nó chỉ là một thực tế (buồn) bạn phải sống với.

Trước hết, tôi sẽ đề xuất sử dụng char.ConvertFromUtf32 để nhận chuỗi ban đầu của bạn. đơn giản hơn nhiều, nhiều hơn nữa có thể đọc được:

var s = char.ConvertFromUtf32(0x10FFFC); 

Vì vậy, của chuỗi Length đây không phải là 1, bởi vì, như tôi đã nói, các thỏa thuận giao diện trong UTF-16 đơn vị mã, không điểm mã Unicode. U + 10FFFC sử dụng hai đơn vị mã UTF-16, vì vậy s.Length là 2. Tất cả các điểm mã trên U + FFFF đều yêu cầu hai đơn vị mã UTF-16 để trình bày.

Bạn nên lưu ý rằng ConvertFromUtf32 không trả lại char: char là đơn vị mã UTF-16, không phải là điểm mã Unicode. Để có thể trả về tất cả các điểm mã Unicode, phương thức đó không thể trả về một đơn char. Đôi khi nó cần phải trả lại hai, và đó là lý do tại sao nó làm cho nó một chuỗi. Đôi khi bạn sẽ tìm thấy một số API giao dịch trong int s thay vì charint có thể được sử dụng để xử lý tất cả các điểm mã quá (đó là những gì ConvertFromUtf32 lấy làm đối số và số ConvertToUtf32 là kết quả).

string thực hiện IEnumerable<char>, có nghĩa là khi bạn lặp lại trên string bạn sẽ nhận được một đơn vị mã UTF-16 cho mỗi lần lặp lại. Đó là lý do tại sao lặp lại chuỗi của bạn và in nó ra mang lại một số sản lượng bị hỏng với hai "thứ" trong đó. Đó là hai đơn vị mã UTF-16 tạo nên sự đại diện của U + 10FFFC.Chúng được gọi là "người thay thế". Người đầu tiên là người thay thế cao/người đứng đầu và người thứ hai là người đại diện thấp/thay thế. Khi bạn in chúng riêng lẻ, chúng không tạo ra đầu ra có ý nghĩa bởi vì người thay thế đơn lẻ thậm chí không hợp lệ trong UTF-16, và chúng cũng không được coi là ký tự Unicode.

Khi bạn nối hai người thay thế đó vào chuỗi trong vòng lặp, bạn sẽ tái tạo lại cặp thay thế một cách hiệu quả và in cặp đó sau làm cho bạn kết quả phù hợp.

Và ở mặt trước ranting, hãy lưu ý rằng không có gì phàn nàn rằng bạn đã sử dụng trình tự UTF-16 không đúng định dạng trong vòng lặp đó. Nó tạo ra một chuỗi với một đại diện duy nhất, và tất cả mọi thứ tiếp tục như không có gì xảy ra: loại string thậm chí không phải là loại cũng được hình thành chuỗi đơn vị mã UTF-16, nhưng loại bất kỳ UTF-16 chuỗi đơn vị mã.

The char structure cung cấp phương pháp tĩnh để đối phó với những người đại diện: IsHighSurrogate, IsLowSurrogate, IsSurrogatePair, ConvertToUtf32, và ConvertFromUtf32. Nếu bạn muốn, bạn có thể viết một iterator rằng lặp trên các ký tự Unicode thay vì UTF-16 đơn vị mã:

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++; 
    } 
} 

Sau đó, bạn có thể lặp lại như:

foreach(int codePoint in s.AsCodePoints()) 
{ 
    // do stuff. codePoint will be an int will value 0x10FFFC in your example 
} 

Nếu bạn muốn nhận được mỗi điểm mã như một chuỗi thay vì thay đổi kiểu trả về để IEnumerable<string> và dòng năng suất để:

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

với phiên bản này, các công việc sau như-là:

foreach(string codePoint in s.AsCodePoints()) 
{ 
    Console.WriteLine(codePoint); 
} 
0

Như posted đã được Martinho, nó là dễ dàng hơn nhiều để tạo ra các chuỗi với điểm mã tin này theo cách đó:

var s = char.ConvertFromUtf32(0x10FFFC); 

Nhưng để lặp qua hai yếu tố char của chuỗi đó là vô nghĩa:

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

Để làm gì? Bạn sẽ chỉ nhận được thay thế cao và thấp mã hóa điểm. Hãy nhớ rằng một char là một loại 16 bit vì vậy nó có thể giữ chỉ là một giá trị tối đa của 0xFFFF. Codepoint của bạn không phù hợp với một loại 16 bit, thực sự cho codepoint cao nhất bạn sẽ cần 21 bit (0x10FFFF) vì vậy loại rộng hơn tiếp theo sẽ chỉ là một loại 32 bit. Hai phần tử char không phải là ký tự, mà là một cặp thay thế. Giá trị của 0x10FFFC được mã hóa thành hai người thay thế.

0

Trong khi @R. câu trả lời Martinho Fernandes là đúng, phương pháp khuyến nông AsCodePoints mình có hai vấn đề:

  1. Nó sẽ ném một ArgumentException vào các điểm mã không hợp lệ (thay thế cao mà không cần thay thế thấp hoặc ngược lại).
  2. Bạn không thể sử dụng char các phương pháp tĩnh mất (char) hoặc (string, int) (chẳng hạn như char.IsNumber()) nếu bạn chỉ có các điểm mã int.

Tôi đã chia mã thành hai phương pháp, một phương pháp tương tự với phương thức gốc nhưng trả về Unicode Replacement Character trên các điểm mã không hợp lệ.Phương pháp thứ hai trả về một struct IEnumerable với các lĩnh vực hữu ích hơn:

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

Trong phạm vi có thể theo luật, người có liên quan Các sản phẩm cùng với tác phẩm này đã từ bỏ tất cả quyền tác giả và quyền liên quan hoặc quyền lân cận o công việc này.

0

Một cách khác để liệt kê các ký tự UTF32 trong chuỗi C# là sử dụng phương thức System.Globalization.StringInfo.GetTextElementEnumerator, như trong mã bên dưới.

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 là cách để thực hiện. Phần còn lại của mã không chính xác. Có một cái nhìn tại: https://msdn.microsoft.com/en-us/library/system.globalization.stringinfo(v=vs.110).aspx – X181

+0

Nó không phải là rõ ràng những gì bạn có ý nghĩa. Có vấn đề với mã từ câu trả lời này không? –

Các vấn đề liên quan