閱讀英文

共用方式為


如何在 .NET 中使用字元編碼類別

本文說明如何使用 .NET 所提供的各種編碼方案來對文字進行編碼和解碼的類別。 指示假設您已閱讀 .NET 中的字元編碼簡介

編碼器和譯碼器

.NET 提供編碼類別,這些類別會使用各種編碼系統來編碼和譯碼文字。 例如,類別 UTF8Encoding 描述UTF-8編碼和譯碼的規則。 .NET 會針對string實例使用UTF-16編碼(由 UnicodeEncoding 類別表示)。 編碼器和譯碼器可用於其他編碼配置。

編碼和譯碼也可以包含驗證。 例如,類別 UnicodeEncoding 會檢查代理範圍中的所有char 實例,以確保它們處於有效的代理配對中。 後援策略會決定編碼器如何處理無效字元,或譯碼器如何處理無效的位元組。

警告

.NET 編碼類別提供儲存和轉換字元數據的方式。 它們不應該用來以字串形式儲存二進位數據。 根據所使用的編碼方式,使用編碼類別將二進位數據轉換成字串格式可能會造成非預期的行為,併產生不正確的或損毀的數據。 若要將二進位資料轉換成字串格式,請使用 Convert.ToBase64String 方法。

.NET 中的所有字元編碼類別都繼承自 System.Text.Encoding 類別,這是一個抽象類,定義所有字元編碼通用的功能。 若要存取 .NET 中實作的個別編碼物件,請執行下列動作:

  • 使用 類別的 Encoding 靜態屬性,其會傳回對象,這些物件代表 .NET 中可用的標準字元編碼方式(ASCII、UTF-7、UTF-8、UTF-16 和 UTF-32)。 例如,Encoding.Unicode屬性會傳回UnicodeEncoding物件。 每個物件都會使用取代後援來處理無法編碼的字串,以及無法譯碼的位元組。 如需詳細資訊,請參閱 替代後備方案

  • 呼叫編碼的類別建構函式。 ASCII、UTF-7、UTF-8、UTF-16 和 UTF-32 編碼的物件可以透過這種方式具現化。 預設情況下,每個物件都會使用替代後備機制來處理無法編碼的字串或無法解碼的位元組,但您可以指定應該改為擲回例外。 如需詳細資訊,請參閱 取代後援例外狀況後援

  • 呼叫建 Encoding(Int32) 構函式,並傳遞代表編碼的整數。 標準編碼物件使用替代後援,而代碼頁和雙位元組字元集 (DBCS) 編碼物件使用最佳匹配後援來處理無法編碼的字串以及無法譯碼的位元組。 如需詳細資訊,請參閱 最適合後援

  • Encoding.GetEncoding呼叫 方法,這個方法會傳回 .NET 中可用的任何標準、代碼頁或 DBCS 編碼。 多載可讓您為編碼器和譯碼器指定後援物件。

您可以呼叫 Encoding.GetEncodings 方法來擷取 .NET 中所有可用編碼的相關信息。 .NET 支援下表所列的字元編碼配置。

編碼類別 說明
ASCII 使用位元組的下七位元來編碼有限範圍的字符。 由於此編碼僅支援從 U+0000U+007F 的字元值,因此在大多數情況下,它不夠適合用於國際化應用程式。
UTF-7 以 7 位 ASCII 字元的序列表示字元。 非 ASCII Unicode 字元是由 ASCII 字元的逸出序列來表示。 UTF-7 支援電子郵件和新聞群組等通訊協定。 不過,UTF-7 並不特別安全或健全。 在某些情況下,變更一個位可能會從根本上改變整個UTF-7字串的解譯。 在其他情況下,不同的UTF-7字串可以編碼相同的文字。 對於包含非 ASCII 字元的序列,UTF-7 需要比 UTF-8 更多的空間,而且編碼/譯碼速度較慢。 因此,您應該盡可能使用UTF-8,而不是UTF-7。
UTF-8 將每個 Unicode 字碼點表示為一到四個字節的序列。 UTF-8 支援 8 位數據大小,且適用於許多現有的作系統。 針對字元的 ASCII 範圍,UTF-8 與 ASCII 編碼相同,並允許更廣泛的字元集。 不過,針對中文-Japanese-Korean (CJK) 腳本,UTF-8 可以針對每個字元要求三個字節,而且可能會造成比 UTF-16 更大的數據大小。 有時候 ASCII 數據量,例如 HTML 標籤,會使 CJK 範圍增加的大小變得合理化。
UTF-16 將每個 Unicode 字碼點表示為一或兩個 16 位整數的序列。 最常見的 Unicode 字元只需要一個 UTF-16 字碼點,儘管 Unicode 增補字元 (U+10000 和更新) 需要兩個 UTF-16 代理字碼點。 支援小端和大端位元組順序。 Common Language Runtime 會使用 UTF-16 編碼來表示 CharString 值,而且 Windows作系統會使用它來表示 WCHAR 值。
UTF-32 以 32 位整數表示每個 Unicode 字碼點。 支援小端和大端字節順序。 當應用程式想要避免在編碼空間至關重要的作業系統中,UTF-16編碼的代理代碼點行為時,會使用UTF-32編碼。 在顯示器上呈現的單一字形仍然可以使用一個以上的UTF-32字元進行編碼。
ANSI/ISO 編碼 提供各種編碼頁的支援。 在 Windows作系統上,代碼頁可用來支援特定語言或語言群組。 如需列出 .NET 所支援代碼頁的數據表,請參閱 類別 Encoding 。 您可以透過呼叫Encoding.GetEncoding(Int32)方法來擷取特定代碼頁的編碼物件。 代碼頁包含 256 個代碼點,且以零起始。 在大部分的代碼頁中,代碼點 0 到 127 代表 ASCII 字元集,而代碼點 128 到 255 在代碼頁之間有很大的差異。 例如,代碼頁 1252 提供拉丁文撰寫系統的字元,包括英文、德文和法文。 代碼頁 1252 中的最後 128 個字碼點包含輔色字元。 代碼頁 1253 提供希臘文撰寫系統中所需的字元碼。 代碼頁 1253 中的最後 128 個字碼點包含希臘文字元。 因此,依賴 ANSI 代碼頁的應用程式無法將希臘文和德文儲存在相同的文字數據流中,除非它包含指出參考代碼頁的識別碼。
雙位元組字元集 (DBCS) 編碼 支援包含超過 256 個字元的語言,例如中文、日文和韓文。 在 DBCS 中,一組代碼點(雙位元組)代表每個字元。 Encoding.IsSingleByte 屬性會傳回 DBCS 編碼的 false。 您可以呼叫 Encoding.GetEncoding(Int32) 方法來擷取特定 DBCS 的編碼物件。 當應用程式處理 DBCS 數據時,DBCS 字元的第一個字節會與緊隨其後的尾端位元組一起處理。 由於單一雙雙位元組代碼點可以根據代碼頁來表示不同的字元,因此此配置仍然不允許在相同的數據流中組合兩種語言,例如日文和中文。

這些編碼可讓您使用 Unicode 字元,以及最常在舊版應用程式中使用的編碼。 此外,您可以藉由定義一個繼承自 Encoding 並覆寫其成員的類別,來建立自定義編碼。

.NET Core 編碼支援

根據預設,.NET Core 不會提供代碼頁 28591 和 Unicode 編碼以外的任何代碼頁編碼,例如 UTF-8 和 UTF-16。 不過,您可以將目標為 .NET 的標準 Windows 應用程式中找到的代碼頁編碼新增至您的應用程式。 如需詳細資訊,請參閱 CodePagesEncodingProvider 主題。

選取編碼類別

如果您有機會選擇應用程式要使用的編碼方式,您應該使用 Unicode 編碼,最好 UTF8Encoding 是 或 UnicodeEncoding。 (.NET 也支援第三個 Unicode 編碼。 UTF32Encoding

如果您打算使用 ASCII 編碼方式 (ASCIIEncoding),請改為選擇 UTF8Encoding 。 這兩種編碼方式與 ASCII 字元集相同,但 UTF8Encoding 具有下列優點:

  • 它可以代表每個 Unicode 字元,而 ASCIIEncoding 只支援 U+0000 與 U+007F 之間的 Unicode 字元值。

  • 它提供錯誤偵測和更好的安全性。

  • 它已微調為盡可能快,而且應該比任何其他編碼更快。 即使是完全 ASCII 的內容,使用 UTF8Encoding 執行的作業也比使用 ASCIIEncoding執行的作業更快。

您應該只考慮針對舊版應用程式使用 ASCIIEncoding 。 不過,即使是舊版應用程式, UTF8Encoding 也可能是下列原因的較佳選擇(假設預設設定):

  • 如果您的應用程式中的內容不完全是 ASCII,而且使用ASCIIEncoding進行編碼,那麼每個非 ASCII 字元都會被編碼為問號(?)。 如果應用程式接著譯碼此數據,則會遺失資訊。

  • 如果您的應用程式中包含非嚴格 ASCII 的內容,並使用 UTF8Encoding 進行編碼,那麼如果將其按 ASCII 解讀,結果會顯得難以理解。 不過,如果應用程式接著使用UTF-8譯碼器來譯碼此數據,則數據會成功執行來回行程。

在 Web 應用程式中,傳送至用戶端以回應 Web 要求的字元應該反映用戶端上所使用的編碼方式。 在大部分情況下,您應該將 HttpResponse.ContentEncoding 屬性設定為 HttpRequest.ContentEncoding 屬性傳回的值,以顯示符合使用者預期編碼的文字。

使用編碼物件

編碼器會將字符串(最常見的是 Unicode 字符)轉換為其數值(位元組)等值表示。 例如,您可以使用 ASCII 編碼器,將 Unicode 字元轉換成 ASCII,以便在控制台中顯示它們。 若要執行轉換,您可以呼叫 Encoding.GetBytes 方法。 如果您想要判斷在執行編碼之前儲存編碼字元所需的位元組數,您可以呼叫 GetByteCount 方法。

下列範例會使用單一位元組陣列,在兩次不同的操作中編碼字串。 它會維護一個索引,用於指示位元組陣列中下一組 ASCII 編碼位元組的起始位置。 它會呼叫 ASCIIEncoding.GetByteCount(String) 方法,以確保位元組數位夠大,足以容納編碼的字元串。 然後它會呼叫 ASCIIEncoding.GetBytes(String, Int32, Int32, Byte[], Int32) 方法來編碼字串中的字元。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      string[] strings= { "This is the first sentence. ",
                          "This is the second sentence. " };
      Encoding asciiEncoding = Encoding.ASCII;

      // Create array of adequate size.
      byte[] bytes = new byte[49];
      // Create index for current position of array.
      int index = 0;

      Console.WriteLine("Strings to encode:");
      foreach (var stringValue in strings) {
         Console.WriteLine($"   {stringValue}");

         int count = asciiEncoding.GetByteCount(stringValue);
         if (count + index >=  bytes.Length)
            Array.Resize(ref bytes, bytes.Length + 50);

         int written = asciiEncoding.GetBytes(stringValue, 0,
                                              stringValue.Length,
                                              bytes, index);

         index = index + written;
      }
      Console.WriteLine("\nEncoded bytes:");
      Console.WriteLine($"{ShowByteValues(bytes, index)}");
      Console.WriteLine();

      // Decode Unicode byte array to a string.
      string newString = asciiEncoding.GetString(bytes, 0, index);
      Console.WriteLine($"Decoded: {newString}");
   }

   private static string ShowByteValues(byte[] bytes, int last )
   {
      string returnString = "   ";
      for (int ctr = 0; ctr <= last - 1; ctr++) {
         if (ctr % 20 == 0)
            returnString += "\n   ";
         returnString += String.Format("{0:X2} ", bytes[ctr]);
      }
      return returnString;
   }
}
// The example displays the following output:
//       Strings to encode:
//          This is the first sentence.
//          This is the second sentence.
//
//       Encoded bytes:
//
//          54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
//          6E 74 65 6E 63 65 2E 20 54 68 69 73 20 69 73 20 74 68 65 20
//          73 65 63 6F 6E 64 20 73 65 6E 74 65 6E 63 65 2E 20
//
//       Decoded: This is the first sentence. This is the second sentence.

將反映特定字元編碼的位元組陣列轉換為字元陣列或字串中的一組字元。 若要將位元組陣組譯碼為字元陣列,您可以呼叫 Encoding.GetChars 方法。 若要將位元組陣組譯碼為字串,您可以呼叫 GetString 方法。 如果您想要判斷在執行譯碼之前儲存譯碼位元組所需的字元數,您可以呼叫 GetCharCount 方法。

下列範例會將三個字串編碼,然後將它們譯碼成單一字元陣列。 它會維護索引,指出下一組譯碼字元的字元陣列中起始位置。 它會呼叫 GetCharCount 方法,以確保字元陣列夠大,足以容納所有解碼的字元。 然後它會呼叫 ASCIIEncoding.GetChars(Byte[], Int32, Int32, Char[], Int32) 方法來對位元組陣列進行譯碼。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      string[] strings = { "This is the first sentence. ",
                           "This is the second sentence. ",
                           "This is the third sentence. " };
      Encoding asciiEncoding = Encoding.ASCII;
      // Array to hold encoded bytes.
      byte[] bytes;
      // Array to hold decoded characters.
      char[] chars = new char[50];
      // Create index for current position of character array.
      int index = 0;

      foreach (var stringValue in strings) {
         Console.WriteLine($"String to Encode: {stringValue}");
         // Encode the string to a byte array.
         bytes = asciiEncoding.GetBytes(stringValue);
         // Display the encoded bytes.
         Console.Write("Encoded bytes: ");
         for (int ctr = 0; ctr < bytes.Length; ctr++)
            Console.Write(" {0}{1:X2}",
                          ctr % 20 == 0 ? Environment.NewLine : "",
                          bytes[ctr]);
         Console.WriteLine();

         // Decode the bytes to a single character array.
         int count = asciiEncoding.GetCharCount(bytes);
         if (count + index >=  chars.Length)
            Array.Resize(ref chars, chars.Length + 50);

         int written = asciiEncoding.GetChars(bytes, 0,
                                              bytes.Length,
                                              chars, index);
         index = index + written;
         Console.WriteLine();
      }

      // Instantiate a single string containing the characters.
      string decodedString = new string(chars, 0, index - 1);
      Console.WriteLine("Decoded string: ");
      Console.WriteLine(decodedString);
   }
}
// The example displays the following output:
//    String to Encode: This is the first sentence.
//    Encoded bytes:
//    54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
//    6E 74 65 6E 63 65 2E 20
//
//    String to Encode: This is the second sentence.
//    Encoded bytes:
//    54 68 69 73 20 69 73 20 74 68 65 20 73 65 63 6F 6E 64 20 73
//    65 6E 74 65 6E 63 65 2E 20
//
//    String to Encode: This is the third sentence.
//    Encoded bytes:
//    54 68 69 73 20 69 73 20 74 68 65 20 74 68 69 72 64 20 73 65
//    6E 74 65 6E 63 65 2E 20
//
//    Decoded string:
//    This is the first sentence. This is the second sentence. This is the third sentence.

衍生自 Encoding 之類別的編碼和譯碼方法的設計目的是處理一組完整的數據;也就是說,所有要編碼或譯碼的數據都會在單一方法呼叫中提供。 不過,在某些情況下,數據流中提供數據,而且要編碼或譯碼的數據只能從個別的讀取作業取得。 這需要編碼或譯碼作業,以記住其先前調用的任何已儲存狀態。 衍生自 EncoderDecoder 之類別的方法,能夠處理跨越多個方法呼叫的編碼和譯碼作業。

Encoder特定編碼的物件可從該編碼的 Encoding.GetEncoder 屬性取得。 Decoder特定編碼的物件可從該編碼的 Encoding.GetDecoder 屬性取得。 針對譯碼作業,請注意衍生自 Decoder 的類別包含 Decoder.GetChars 方法,但是它們沒有對應至 Encoding.GetString的方法。

下列範例說明使用 Encoding.GetStringDecoder.GetChars 方法來譯碼 Unicode 位元組陣列之間的差異。 此範例會將包含某些 Unicode 字元的字串編碼為檔案,然後使用兩個譯碼方法一次譯碼十個字節。 因為代理對會在第十個和第十一個字節中發生,所以會透過不同的方法進行解碼。 如輸出所示,Encoding.GetString 方法無法正確解碼這些位元組,而是以 U+FFFD (替代字符)取代它們。 另一方面, Decoder.GetChars 方法可以成功譯碼位元組數位列以取得原始字串。

using System;
using System.IO;
using System.Text;

public class Example
{
   public static void Main()
   {
      // Use default replacement fallback for invalid encoding.
      UnicodeEncoding enc = new UnicodeEncoding(true, false, false);

      // Define a string with various Unicode characters.
      string str1 = "AB YZ 19 \uD800\udc05 \u00e4";
      str1 += "Unicode characters. \u00a9 \u010C s \u0062\u0308";
      Console.WriteLine("Created original string...\n");

      // Convert string to byte array.
      byte[] bytes = enc.GetBytes(str1);

      FileStream fs = File.Create(@".\characters.bin");
      BinaryWriter bw = new BinaryWriter(fs);
      bw.Write(bytes);
      bw.Close();

      // Read bytes from file.
      FileStream fsIn = File.OpenRead(@".\characters.bin");
      BinaryReader br = new BinaryReader(fsIn);

      const int count = 10;            // Number of bytes to read at a time.
      byte[] bytesRead = new byte[10]; // Buffer (byte array).
      int read;                        // Number of bytes actually read.
      string str2 = String.Empty;      // Decoded string.

      // Try using Encoding object for all operations.
      do {
         read = br.Read(bytesRead, 0, count);
         str2 += enc.GetString(bytesRead, 0, read);
      } while (read == count);
      br.Close();
      Console.WriteLine("Decoded string using UnicodeEncoding.GetString()...");
      CompareForEquality(str1, str2);
      Console.WriteLine();

      // Use Decoder for all operations.
      fsIn = File.OpenRead(@".\characters.bin");
      br = new BinaryReader(fsIn);
      Decoder decoder = enc.GetDecoder();
      char[] chars = new char[50];
      int index = 0;                   // Next character to write in array.
      int written = 0;                 // Number of chars written to array.
      do {
         read = br.Read(bytesRead, 0, count);
         if (index + decoder.GetCharCount(bytesRead, 0, read) - 1 >= chars.Length)
            Array.Resize(ref chars, chars.Length + 50);

         written = decoder.GetChars(bytesRead, 0, read, chars, index);
         index += written;
      } while (read == count);
      br.Close();
      // Instantiate a string with the decoded characters.
      string str3 = new String(chars, 0, index);
      Console.WriteLine("Decoded string using UnicodeEncoding.Decoder.GetString()...");
      CompareForEquality(str1, str3);
   }

   private static void CompareForEquality(string original, string decoded)
   {
      bool result = original.Equals(decoded);
      Console.WriteLine($"original = decoded: {original.Equals(decoded, StringComparison.Ordinal)}");
      if (! result) {
         Console.WriteLine("Code points in original string:");
         foreach (var ch in original)
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
         Console.WriteLine();

         Console.WriteLine("Code points in decoded string:");
         foreach (var ch in decoded)
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//    Created original string...
//
//    Decoded string using UnicodeEncoding.GetString()...
//    original = decoded: False
//    Code points in original string:
//    0041 0042 0020 0059 005A 0020 0031 0039 0020 D800 DC05 0020 00E4 0055 006E 0069 0063 006F
//    0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
//    0020 0073 0020 0062 0308
//    Code points in decoded string:
//    0041 0042 0020 0059 005A 0020 0031 0039 0020 FFFD FFFD 0020 00E4 0055 006E 0069 0063 006F
//    0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
//    0020 0073 0020 0062 0308
//
//    Decoded string using UnicodeEncoding.Decoder.GetString()...
//    original = decoded: True

選擇後援策略

當方法嘗試編碼或譯碼字元但不存在對應時,它必須實作後援策略,以決定應該如何處理失敗的對應。 後援策略有三種類型:

  • 最適合後援

  • 替代備援

  • 例外備援

重要

當 Unicode 字元無法對應至特定代碼頁編碼時,編碼作業中最常見的問題就會發生。 譯碼作業中最常見的問題發生在無效的位元組序列無法轉譯成有效的 Unicode 字元時。 基於這些原因,您應該知道特定編碼物件所使用的後援策略。 盡可能指定編碼物件在具現化物件時所使用的後援策略。

Best-Fit 後援

當字元在目標編碼中沒有完全相符專案時,編碼器可以嘗試將它對應至類似的字元。 最適合的後援主要是編碼的問題,而不是解碼的問題。只有極少數的代碼頁會包含無法成功對應到 Unicode 的字元。最適合的後援是從 Encoding.GetEncoding(Int32)Encoding.GetEncoding(String) 的多載方法所擷取的代碼頁和雙位元組字元集編碼的預設值。

注意

理論上,.NET(UTF8EncodingUnicodeEncodingUTF32Encoding)中提供的 Unicode 編碼類別支援每個字元集中的每個字元,因此可用來消除替代回退問題。

最適合的策略會因不同的代碼頁而有所不同。 例如,對於某些代碼頁,全角拉丁字元會對應至較常見的半角拉丁字元。 若為其他代碼頁,則不會進行映射。 即使在積極的最佳匹配策略下,在某些編碼中,某些字元也找不到合適的對應。 例如,中文字形沒有與代碼頁碼 1252 的合理對應。 在此情況下,會使用取代字串。 根據預設,此字串只是單一問號(U+003F)。

注意

最佳調整策略不會詳細記載。 不過, Unicode 聯盟 網站記載了數個代碼頁。 請檢閱該資料夾中的 readme.txt 檔案,了解如何解讀對應檔案。

下列範例使用代碼頁 1252(西歐語言的 Windows 代碼頁)來示範最適合的對應方式及其缺點。 方法 Encoding.GetEncoding(Int32) 可用來檢索代碼頁 1252 的編碼物件。 預設情況下,它對於不支援的 Unicode 字元使用最佳匹配的映射。 此範例會建立一個字串,其中包含三個非 ASCII 字元 - 帶圓圈的拉丁大寫字母 S (U+24C8)、上標五 (U+2075) 和無限大 (U+221E),並以空格分隔。 如範例的輸出所示,當字串編碼時,三個原始的非空格字元會取代為 QUESTION MARK (U+003F)、DIGIT FIVE (U+0035)和 DIGIT8 (U+0038)。 數字8是無法支援的INFINITY字元的一個非常差的替代品,而問號表示原始字元沒有適當的對應。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      // Get an encoding for code page 1252 (Western Europe character set).
      Encoding cp1252 = Encoding.GetEncoding(1252);

      // Define and display a string.
      string str = "\u24c8 \u2075 \u221e";
      Console.WriteLine("Original string: " + str);
      Console.Write("Code points in string: ");
      foreach (var ch in str)
         Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

      Console.WriteLine("\n");

      // Encode a Unicode string.
      Byte[] bytes = cp1252.GetBytes(str);
      Console.Write("Encoded bytes: ");
      foreach (byte byt in bytes)
         Console.Write("{0:X2} ", byt);
      Console.WriteLine("\n");

      // Decode the string.
      string str2 = cp1252.GetString(bytes);
      Console.WriteLine($"String round-tripped: {str.Equals(str2)}");
      if (! str.Equals(str2)) {
         Console.WriteLine(str2);
         foreach (var ch in str2)
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
      }
   }
}
// The example displays the following output:
//       Original string: Ⓢ ⁵ ∞
//       Code points in string: 24C8 0020 2075 0020 221E
//
//       Encoded bytes: 3F 20 35 20 38
//
//       String round-tripped: False
//       ? 5 8
//       003F 0020 0035 0020 0038

最適合的對應是Encoding 對象的預設的行為,該物件將 Unicode 資料編碼為碼頁資料,還有依賴這種行為的舊版應用程式。 不過,基於安全性考慮,大部分的新應用程式都應該避免使用最佳匹配行為。 例如,應用程式不應該採用最佳適配的編碼來處理網域名稱。

注意

您也可以實作編碼的自定義最佳適配回退對應。 如需詳細資訊,請參閱 實作自定義後援策略 一節。

如果"最佳適合回退"是編碼物件的預設值,您可以在透過呼叫 Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback)Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 多載來擷取 Encoding 物件時,選擇另一種回退策略。 下一節包含一個範例,將無法對應至代碼頁 1252 的每個字元取代為星號 。。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      Encoding cp1252r = Encoding.GetEncoding(1252,
                                  new EncoderReplacementFallback("*"),
                                  new DecoderReplacementFallback("*"));

      string str1 = "\u24C8 \u2075 \u221E";
      Console.WriteLine(str1);
      foreach (var ch in str1)
         Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

      Console.WriteLine();

      byte[] bytes = cp1252r.GetBytes(str1);
      string str2 = cp1252r.GetString(bytes);
      Console.WriteLine($"Round-trip: {str1.Equals(str2)}");
      if (! str1.Equals(str2)) {
         Console.WriteLine(str2);
         foreach (var ch in str2)
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//       Ⓢ ⁵ ∞
//       24C8 0020 2075 0020 221E
//       Round-trip: False
//       * * *
//       002A 0020 002A 0020 002A

替代備援

當字元在目標配置中沒有完全一致的字元,且沒有任何適當的字元可以對應時,應用程式可以指定替代的字元或字串。 這是 Unicode 解碼器的預設行為,它會將任何無法解碼的兩字節序列替換為 REPLACEMENT_CHARACTER (U+FFFD)。 這也是 類別的預設行為 ASCIIEncoding ,它會以問號取代無法編碼或譯碼的每個字元。 下列範例說明先前範例中 Unicode 字串的字元取代。 如輸出所示,無法解碼為 ASCII 位元組值的每個字元都會由 0x3F 取代,這是問號的 ASCII 碼。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      Encoding enc = Encoding.ASCII;

      string str1 = "\u24C8 \u2075 \u221E";
      Console.WriteLine(str1);
      foreach (var ch in str1)
         Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

      Console.WriteLine("\n");

      // Encode the original string using the ASCII encoder.
      byte[] bytes = enc.GetBytes(str1);
      Console.Write("Encoded bytes: ");
      foreach (var byt in bytes)
         Console.Write("{0:X2} ", byt);
      Console.WriteLine("\n");

      // Decode the ASCII bytes.
      string str2 = enc.GetString(bytes);
      Console.WriteLine($"Round-trip: {str1.Equals(str2)}");
      if (! str1.Equals(str2)) {
         Console.WriteLine(str2);
         foreach (var ch in str2)
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//       Ⓢ ⁵ ∞
//       24C8 0020 2075 0020 221E
//
//       Encoded bytes: 3F 20 3F 20 3F
//
//       Round-trip: False
//       ? ? ?
//       003F 0020 003F 0020 003F

.NET 包含 EncoderReplacementFallbackDecoderReplacementFallback 類別,當字元在編碼或解碼過程中無法完全對應時,這些類別會用替代字串進行替換。 根據預設,此替換字串為問號,但您可以呼叫類別的建構函式重載來選擇其他字串。 一般而言,取代字串是單一字元,但這不是必須的要求。 下列範例透過實例化 EncoderReplacementFallback 物件,該物件使用星號 (*) 作為取代字串,來改變代碼頁 1252 編碼器的行為。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      Encoding cp1252r = Encoding.GetEncoding(1252,
                                  new EncoderReplacementFallback("*"),
                                  new DecoderReplacementFallback("*"));

      string str1 = "\u24C8 \u2075 \u221E";
      Console.WriteLine(str1);
      foreach (var ch in str1)
         Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

      Console.WriteLine();

      byte[] bytes = cp1252r.GetBytes(str1);
      string str2 = cp1252r.GetString(bytes);
      Console.WriteLine($"Round-trip: {str1.Equals(str2)}");
      if (! str1.Equals(str2)) {
         Console.WriteLine(str2);
         foreach (var ch in str2)
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

         Console.WriteLine();
      }
   }
}
// The example displays the following output:
//       Ⓢ ⁵ ∞
//       24C8 0020 2075 0020 221E
//       Round-trip: False
//       * * *
//       002A 0020 002A 0020 002A

注意

您也可以實作編碼的取代類別。 如需詳細資訊,請參閱 實作自定義後援策略 一節。

除了問號(U+003F),Unicode 替代字元(U+FFFD)通常用來作為替代字元,尤其是在解碼時無法成功轉譯成 Unicode 字元的位元序列時。 不過,您可以選擇任何取代字串,而且可以包含多個字元。

例外備援

如果編碼器無法編碼一組字元,則可以擲回EncoderFallbackException;如果解碼器無法解碼位元組陣列,則可以擲回DecoderFallbackException。 在編碼和解碼作業中擲回例外狀況時,請將 EncoderExceptionFallback 物件和 DecoderExceptionFallback 物件分別提供給 Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 方法。 以下範例說明 ASCIIEncoding 類別的異常回退機制。

using System;
using System.Text;

public class Example
{
   public static void Main()
   {
      Encoding enc = Encoding.GetEncoding("us-ascii",
                                          new EncoderExceptionFallback(),
                                          new DecoderExceptionFallback());

      string str1 = "\u24C8 \u2075 \u221E";
      Console.WriteLine(str1);
      foreach (var ch in str1)
         Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

      Console.WriteLine("\n");

      // Encode the original string using the ASCII encoder.
      byte[] bytes = {};
      try {
         bytes = enc.GetBytes(str1);
         Console.Write("Encoded bytes: ");
         foreach (var byt in bytes)
            Console.Write("{0:X2} ", byt);

         Console.WriteLine();
      }
      catch (EncoderFallbackException e) {
         Console.Write("Exception: ");
         if (e.IsUnknownSurrogate())
            Console.WriteLine($"Unable to encode surrogate pair 0x{Convert.ToUInt16(e.CharUnknownHigh):X4} 0x{Convert.ToUInt16(e.CharUnknownLow):X3} at index {e.Index}.");
         else
            Console.WriteLine($"Unable to encode 0x{Convert.ToUInt16(e.CharUnknown):X4} at index {e.Index}.");
         return;
      }
      Console.WriteLine();

      // Decode the ASCII bytes.
      try {
         string str2 = enc.GetString(bytes);
         Console.WriteLine($"Round-trip: {str1.Equals(str2)}");
         if (! str1.Equals(str2)) {
            Console.WriteLine(str2);
            foreach (var ch in str2)
               Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

            Console.WriteLine();
         }
      }
      catch (DecoderFallbackException e) {
         Console.Write("Unable to decode byte(s) ");
         foreach (byte unknown in e.BytesUnknown)
            Console.Write("0x{0:X2} ");

         Console.WriteLine($"at index {e.Index}");
      }
   }
}
// The example displays the following output:
//       Ⓢ ⁵ ∞
//       24C8 0020 2075 0020 221E
//
//       Exception: Unable to encode 0x24C8 at index 0.

注意

您也可以實作編碼作業的自定義例外狀況處理程式。 如需詳細資訊,請參閱 實作自定義後援策略 一節。

EncoderFallbackExceptionDecoderFallbackException 物件提供有關造成例外狀況之條件的下列資訊:

EncoderFallbackException雖然 和 DecoderFallbackException 物件提供關於例外狀況的適當診斷資訊,但是它們不會提供編碼或譯碼緩衝區的存取權。 因此,它們不允許在編碼或譯碼方法內取代或更正無效的數據。

實作自定義後援策略

除了由代碼頁在內部實現的最適匹配之外,.NET 還包含下列類別用於實現後援策略:

此外,您可以遵循下列步驟,實作使用最適合後援、取代後援或例外狀況後援的自定義解決方案:

  1. 從衍生類別 EncoderFallback 以進行編碼作業,以及 DecoderFallback 從 進行譯碼作業。

  2. 從衍生類別 EncoderFallbackBuffer 以進行編碼作業,以及 DecoderFallbackBuffer 從 進行譯碼作業。

  3. 針對例外狀況後援,如果預先定義的 EncoderFallbackExceptionDecoderFallbackException 類別不符合您的需求,請從 或 等ExceptionArgumentException例外狀況物件衍生類別。

衍生自 EncoderFallback 或 DecoderFallback

若要實作自定義後援解決方案,您必須建立繼承自 EncoderFallback 的類別以進行編碼作業,以及從 DecoderFallback 進行譯碼作業。 這些類別的實例會傳遞至 方法, Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 並做為編碼類別與後援實作之間的媒介。

當您為編碼器或譯碼器建立自定義後援解決方案時,必須實作下列成員:

派生自 EncoderFallbackBuffer 或 DecoderFallbackBuffer

若要實作自定義後援解決方案,您也必須建立繼承自 EncoderFallbackBuffer 的類別以進行編碼作業,以及從 DecoderFallbackBuffer 進行譯碼作業。 這些類別的實例是由 EncoderFallback 類別的方法和 DecoderFallback 類別的方法傳回。 當 EncoderFallback.CreateFallbackBuffer 編碼器遇到無法編碼的第一個字元時,編碼器會呼叫 方法,而且 DecoderFallback.CreateFallbackBuffer 當譯碼器遇到無法譯碼的一或多個字節時,由譯碼器呼叫方法。 EncoderFallbackBufferDecoderFallbackBuffer 類別提供後援實作。 每個實例都代表緩衝區,其中包含後援字元,將取代無法編碼的字元或無法譯碼的位元組序列。

當您為編碼器或譯碼器建立自定義後援解決方案時,必須實作下列成員:

如果後援實作是最適配的後援或替代後援,則從 EncoderFallbackBufferDecoderFallbackBuffer 衍生的類別也會維護兩個私有實例欄位:緩衝區中的確切字元數,以及緩衝區中下一個要返回的字元索引。

EncoderFallback 範例

過去的範例使用替代後援機制,將未對應到ASCII字元的Unicode字元替換為星號 (*)。 下列範例會改用自定義的最佳適配後備方案實作,以提供較佳的非 ASCII 字符對應。

下列程式代碼會定義衍生自 EncoderFallbackCustomMapper類別,以處理非 ASCII 字元的最佳對應。 其 CreateFallbackBuffer 方法會傳回一個 CustomMapperFallbackBuffer 物件,該物件提供 EncoderFallbackBuffer 的實作。 類別 CustomMapper 使用 Dictionary<TKey,TValue> 物件來儲存不支援的 Unicode 字元(作為索引鍵值)及其對應的 8 位元字元映射(儲存在 64 位整數中的兩個連續位元組)。 若要將此對應提供給後援緩衝區,CustomMapper 實例會作為參數傳遞至類別 CustomMapperFallbackBuffer 建構函式。 因為最長的對應是 Unicode 字元 U+221E 的字串 “INF”,因此 MaxCharCount 屬性會傳回 3。

public class CustomMapper : EncoderFallback
{
   public string DefaultString;
   internal Dictionary<ushort, ulong> mapping;

   public CustomMapper() : this("*")
   {
   }

   public CustomMapper(string defaultString)
   {
      this.DefaultString = defaultString;

      // Create table of mappings
      mapping = new Dictionary<ushort, ulong>();
      mapping.Add(0x24C8, 0x53);
      mapping.Add(0x2075, 0x35);
      mapping.Add(0x221E, 0x49004E0046);
   }

   public override EncoderFallbackBuffer CreateFallbackBuffer()
   {
      return new CustomMapperFallbackBuffer(this);
   }

   public override int MaxCharCount
   {
      get { return 3; }
   }
}

下列程式代碼會定義 衍生自 EncoderFallbackBufferCustomMapperFallbackBuffer 類別。 包含最佳匹配對應並且在 CustomMapper 實例中定義的字典可以從其類別建構函式中取得。 如果 ASCII 編碼器無法編碼的任何 Unicode 字元定義在對應字典中,則其 Fallback 方法會傳回 true,否則會傳回 false。 針對每個回退機制,私有變數 count 會指出剩餘要傳回的字元數,而私有變數 index 會指出字串緩衝區 charsToReturn 中的下一個要傳回字元的位置。

public class CustomMapperFallbackBuffer : EncoderFallbackBuffer
{
   int count = -1;                   // Number of characters to return
   int index = -1;                   // Index of character to return
   CustomMapper fb;
   string charsToReturn;

   public CustomMapperFallbackBuffer(CustomMapper fallback)
   {
      this.fb = fallback;
   }

   public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)
   {
      // Do not try to map surrogates to ASCII.
      return false;
   }

   public override bool Fallback(char charUnknown, int index)
   {
      // Return false if there are already characters to map.
      if (count >= 1) return false;

      // Determine number of characters to return.
      charsToReturn = String.Empty;

      ushort key = Convert.ToUInt16(charUnknown);
      if (fb.mapping.ContainsKey(key)) {
         byte[] bytes = BitConverter.GetBytes(fb.mapping[key]);
         int ctr = 0;
         foreach (var byt in bytes) {
            if (byt > 0) {
               ctr++;
               charsToReturn += (char) byt;
            }
         }
         count = ctr;
      }
      else {
         // Return default.
         charsToReturn = fb.DefaultString;
         count = 1;
      }
      this.index = charsToReturn.Length - 1;

      return true;
   }

   public override char GetNextChar()
   {
      // We'll return a character if possible, so subtract from the count of chars to return.
      count--;
      // If count is less than zero, we've returned all characters.
      if (count < 0)
         return '\u0000';

      this.index--;
      return charsToReturn[this.index + 1];
   }

   public override bool MovePrevious()
   {
      // Original: if count >= -1 and pos >= 0
      if (count >= -1) {
         count++;
         return true;
      }
      else {
         return false;
      }
   }

   public override int Remaining
   {
      get { return count < 0 ? 0 : count; }
   }

   public override void Reset()
   {
      count = -1;
      index = -1;
   }
}

下列程式代碼接著會具現化 物件, CustomMapper 並將它的實例傳遞至 Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) 方法。 輸出表示最適配後援實作已成功處理原始字串中的三個非 ASCII 字元。

using System;
using System.Collections.Generic;
using System.Text;

class Program
{
   static void Main()
   {
      Encoding enc = Encoding.GetEncoding("us-ascii", new CustomMapper(), new DecoderExceptionFallback());

      string str1 = "\u24C8 \u2075 \u221E";
      Console.WriteLine(str1);
      for (int ctr = 0; ctr <= str1.Length - 1; ctr++) {
         Console.Write("{0} ", Convert.ToUInt16(str1[ctr]).ToString("X4"));
         if (ctr == str1.Length - 1)
            Console.WriteLine();
      }
      Console.WriteLine();

      // Encode the original string using the ASCII encoder.
      byte[] bytes = enc.GetBytes(str1);
      Console.Write("Encoded bytes: ");
      foreach (var byt in bytes)
         Console.Write("{0:X2} ", byt);

      Console.WriteLine("\n");

      // Decode the ASCII bytes.
      string str2 = enc.GetString(bytes);
      Console.WriteLine($"Round-trip: {str1.Equals(str2)}");
      if (! str1.Equals(str2)) {
         Console.WriteLine(str2);
         foreach (var ch in str2)
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

         Console.WriteLine();
      }
   }
}

另請參閱


其他資源