Поделиться через


Использование классов кодировки символов в .NET

В этой статье объясняется, как использовать классы, предоставляемые .NET для кодирования и декодирования текста с помощью различных схем кодирования. В инструкциях предполагается, что вы прочитали введение в кодировку символов в .NET.

Кодировщики и декодеры

.NET предоставляет классы кодирования, которые кодируют и декодируют текст с помощью различных систем кодирования. Например, UTF8Encoding класс описывает правила кодирования и декодирования из UTF-8. В .NET для UnicodeEncoding экземпляров используется кодировка UTF-16 (представленная классомstring). Кодировщики и декодеры доступны для других схем кодирования.

Кодирование и декодирование также могут включать проверку. Например, класс 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 DBCS, доступную в .NET. Перегрузки позволяют указать резервный объект для кодировщика и декодатора.

Вы можете получить сведения обо всех кодировках, доступных в .NET, вызвав Encoding.GetEncodings метод. .NET поддерживает схемы кодировки символов, перечисленные в следующей таблице.

Класс кодировки Описание
ASCII Кодирует ограниченный диапазон символов с помощью нижних семи битов байта. Так как эта кодировка поддерживает только значения символов от U+0000 до U+007F, в большинстве случаев она недостаточна для интернационализированных приложений.
UTF-7 Представляет символы в виде последовательностей 7-разрядных символов ASCII. Символы Юникода, отличные от ASCII, представлены escape-последовательностью символов ASCII. UTF-7 поддерживает такие протоколы, как электронная почта и группа новостей. Однако UTF-7 не является особенно безопасным или надежным. В некоторых случаях изменение одного бита может радикально изменить интерпретацию всей строки UTF-7. В других случаях разные строки UTF-7 могут кодировать один и тот же текст. Для последовательностей, включающих символы, отличные от ASCII, UTF-7 требует больше места, чем UTF-8, а кодировка и декодирование медленнее. Следовательно, по возможности следует использовать UTF-8 вместо UTF-7.
UTF-8 Представляет каждую кодовую точку Юникода в виде последовательности от одного до четырех байтов. UTF-8 поддерживает 8-разрядные размеры данных и хорошо работает со многими существующими операционными системами. Для диапазона символов ASCII UTF-8 идентичен кодировке ASCII и позволяет более широкому набору символов. Однако для символовJapanese-Korean (CJK) UTF-8 может требовать три байта для каждого символа и может привести к увеличению объема данных по сравнению с UTF-16. Иногда объем данных ASCII, например html-тегов, оправдывает увеличение размера диапазона CJK.
UTF-16 Представляет каждую точку кода Юникода в виде последовательности одного или двух 16-разрядных целых чисел. Для большинства распространенных символов Юникода требуется только одна кодовая точка UTF-16, хотя дополнительные символы Юникода (U+10000 и больше) требуют двух суррогатных точек кода UTF-16. Поддерживаются как маленькие, так и большие байтовые заказы. Кодировка UTF-16 используется средой CLR для представления Char и String значений, и она используется операционной системой Windows для представления WCHAR значений.
UTF-32 Представляет каждую точку кода Юникода в виде 32-разрядного целого числа. Поддерживаются как маленькие, так и большие байтовые заказы. Кодировка UTF-32 используется, когда приложения хотят избежать суррогатного поведения кодирования точек кода UTF-16 в операционных системах, для которых закодированное пространство слишком важно. Одиночные глифы, отрисованные на дисплее, по-прежнему могут быть закодированы несколькими символами UTF-32.
Кодировка ANSI/ISO Предоставляет поддержку различных кодовых страниц. В операционных системах Windows кодовые страницы используются для поддержки определенного языка или группы языков. Для получения таблицы, в которой перечислены кодовые страницы, поддерживаемые .NET, см. класс Encoding. Вы можете получить объект кодирования для определенной кодовой страницы, вызвав Encoding.GetEncoding(Int32) метод. Кодовая страница содержит 256 точек кода и основана на нулях. В большинстве кодовых страниц кодовые точки от 0 до 127 представляют набор символов ASCII, а кодовые точки 128–255 значительно отличаются между кодовых страницами. Например, кодовая страница 1252 предоставляет символы для латинских систем написания, включая английский, немецкий и французский. Последние 128 кодовых точек на кодовой странице 1252 содержат символы акцента. Кодовая страница 1253 предоставляет коды символов, необходимые в греческой системе написания. Последние 128 кодовых точек на кодовой странице 1253 содержат греческие символы. В результате приложение, использующее кодовые страницы ANSI, не может хранить греческий и немецкий в том же текстовом потоке, если он не содержит идентификатор, указывающий на указанную кодовую страницу.
Кодировки двойного байтового набора символов (DBCS) Поддерживает языки, такие как китайский, японский и корейский, содержащие более 256 символов. В DBCS пара точек кода (двойной байт) представляет каждый символ. Свойство Encoding.IsSingleByte возвращает false для кодировок DBCS. Вы можете получить объект кодирования для определенного DBCS, вызвав Encoding.GetEncoding(Int32) метод. Когда приложение обрабатывает данные DBCS, первый байт символа DBCS (ведущий байт) обрабатывается в сочетании с замыкающим байтом, который следует непосредственно за ним. Так как одна пара двухбайтовых точек кода может представлять разные символы в зависимости от кодовой страницы, эта схема по-прежнему не допускает сочетание двух языков, таких как японский и китайский, в одном потоке данных.

Эти кодировки позволяют работать с символами Юникода, а также с кодировками, которые чаще всего используются в устаревших приложениях. Кроме того, можно создать настраиваемую кодировку, определив класс, наследующий от Encoding и переопределяющий его элементы.

Поддержка кодирования .NET Core

По умолчанию .NET Core не предоставляет доступ к кодировкам кодовой страницы, за исключением кодовой страницы 28591 и кодировок Юникода, таких как UTF-8 и UTF-16. Однако вы можете добавить кодовые страницы кодировки, которые используются в стандартных приложениях Windows, предназначенных для .NET, в своё приложение. Дополнительные сведения см. в CodePagesEncodingProvider разделе.

Выбор класса кодировки

Если у вас есть возможность выбрать кодировку, используемую приложением, следует использовать кодировку Юникода, предпочтительно UTF8Encoding или UnicodeEncoding. (.NET также поддерживает третью кодировку Юникода, UTF32Encoding.)

Если вы планируете использовать кодировку ASCII (ASCIIEncoding), выберите UTF8Encoding вместо этого. Две кодировки идентичны для набора символов ASCII, но UTF8Encoding имеет следующие преимущества:

  • Он может представлять каждый символ Юникода, в то время как ASCIIEncoding поддерживает только значения символов Юникода между U+0000 и U+007F.

  • Он обеспечивает обнаружение ошибок и более высокую безопасность.

  • Он был настроен на максимальную скорость и должен работать быстрее, чем любая другая кодировка. Даже для содержимого, полностью являющегося ASCII, операции с UTF8Encoding выполняются быстрее, чем операции, выполняемые с помощью ASCIIEncoding.

Рекомендуется использовать ASCIIEncoding только для устаревших приложений. Однако даже для устаревших приложений UTF8Encoding может быть лучшим вариантом по следующим причинам (при условии, что параметры по умолчанию):

  • Если приложение содержит содержимое, которое не является строго ASCII и кодирует его с ASCIIEncoding помощью, каждый символ, отличный от ASCII, кодируется как вопросительный знак (?). Если приложение декодирует эти данные, данные будут потеряны.

  • Если ваше приложение содержит контент, который не является строго ASCII, и кодирует его с помощью UTF8Encoding, результат будет нечитабельным, если его интерпретировать как ASCII. Однако если приложение использует декодатор UTF-8 для декодирования этих данных, данные успешно выполняют круговую поездку.

В веб-приложении символы, отправленные клиенту в ответ на веб-запрос, должны отражать кодировку, используемую для клиента. В большинстве случаев следует установить свойство HttpResponse.ContentEncoding на значение, возвращаемое свойством HttpRequest.ContentEncoding, чтобы отобразить текст в ожидаемой пользователем кодировке.

Использование объекта кодировки

Кодировщик преобразует строку символов (чаще всего символов Юникода) в его эквивалент числовых (байтов). Например, можно использовать кодировщик ASCII для преобразования символов Юникода в 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.
Imports System.Text

Module Example
    Public Sub Main()
        Dim strings() As String = {"This is the first sentence. ",
                                    "This is the second sentence. "}
        Dim asciiEncoding As Encoding = Encoding.ASCII

        ' Create array of adequate size.
        Dim bytes(50) As Byte
        ' Create index for current position of array.
        Dim index As Integer = 0

        Console.WriteLine("Strings to encode:")
        For Each stringValue In strings
            Console.WriteLine("   {0}", stringValue)

            Dim count As Integer = asciiEncoding.GetByteCount(stringValue)
            If count + index >= bytes.Length Then
                Array.Resize(bytes, bytes.Length + 50)
            End If
            Dim written As Integer = asciiEncoding.GetBytes(stringValue, 0,
                                                            stringValue.Length,
                                                            bytes, index)

            index = index + written
        Next
        Console.WriteLine()
        Console.WriteLine("Encoded bytes:")
        Console.WriteLine("{0}", ShowByteValues(bytes, index))
        Console.WriteLine()

        ' Decode Unicode byte array to a string.
        Dim newString As String = asciiEncoding.GetString(bytes, 0, index)
        Console.WriteLine("Decoded: {0}", newString)
    End Sub

    Private Function ShowByteValues(bytes As Byte(), last As Integer) As String
        Dim returnString As String = "   "
        For ctr As Integer = 0 To last - 1
            If ctr Mod 20 = 0 Then returnString += vbCrLf + "   "
            returnString += String.Format("{0:X2} ", bytes(ctr))
        Next
        Return returnString
    End Function
End Module
' 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.
Imports System.Text

Module Example
    Public Sub Main()
        Dim strings() As String = {"This is the first sentence. ",
                                    "This is the second sentence. ",
                                    "This is the third sentence. "}
        Dim asciiEncoding As Encoding = Encoding.ASCII
        ' Array to hold encoded bytes.
        Dim bytes() As Byte
        ' Array to hold decoded characters.
        Dim chars(50) As Char
        ' Create index for current position of character array.
        Dim index As Integer

        For Each stringValue In strings
            Console.WriteLine("String to Encode: {0}", stringValue)
            ' Encode the string to a byte array.
            bytes = asciiEncoding.GetBytes(stringValue)
            ' Display the encoded bytes.
            Console.Write("Encoded bytes: ")
            For ctr As Integer = 0 To bytes.Length - 1
                Console.Write(" {0}{1:X2}", If(ctr Mod 20 = 0, vbCrLf, ""),
                                            bytes(ctr))
            Next
            Console.WriteLine()

            ' Decode the bytes to a single character array.
            Dim count As Integer = asciiEncoding.GetCharCount(bytes)
            If count + index >= chars.Length Then
                Array.Resize(chars, chars.Length + 50)
            End If
            Dim written As Integer = asciiEncoding.GetChars(bytes, 0,
                                                            bytes.Length,
                                                            chars, index)
            index = index + written
            Console.WriteLine()
        Next

        ' Instantiate a single string containing the characters.
        Dim decodedString As New String(chars, 0, index - 1)
        Console.WriteLine("Decoded string: ")
        Console.WriteLine(decodedString)
    End Sub
End Module
' 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 класса, предназначены для работы с полным набором данных. То есть все данные, которые необходимо закодировать или декодировать, предоставляются в одном вызове метода. Однако в некоторых случаях данные доступны в потоке, а данные, которые должны быть закодированы или декодированы, могут быть доступны только из отдельных операций чтения. Для этого требуется операция кодирования или декодирования, чтобы запомнить любое сохраненное состояние из предыдущего вызова. Методы классов, производных от Encoder и Decoder, способны обрабатывать операции кодирования и декодирования, охватывающие несколько вызовов методов.

Объект Encoder для определенной кодировки доступен из этого свойства кодирования Encoding.GetEncoder . Decoder Объект для определённой кодировки можно получить из свойства этой кодировкиEncoding.GetDecoder. Для операций декодирования обратите внимание, что классы, производные от Decoder, включают метод Decoder.GetChars, но не имеют метода, соответствующего Encoding.GetString.

В следующем примере показано различие между использованием Encoding.GetString и Decoder.GetChars методами для декодирования массива байтов Юникода. В этом примере строка, содержащая некоторые символы Юникода, кодируется в файл, а затем два метода используются для их декодирования по десять байтов за раз. Так как суррогатная пара возникает в десятых и одиннадцатых байтах, она декодируется в отдельных вызовах метода. Как показано в выходных данных, метод 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
Imports System.IO
Imports System.Text

Module Example
    Public Sub Main()
        ' Use default replacement fallback for invalid encoding.
        Dim enc As New UnicodeEncoding(True, False, False)

        ' Define a string with various Unicode characters.
        Dim str1 As String = String.Format("AB YZ 19 {0}{1} {2}",
                                           ChrW(&hD800), ChrW(&hDC05), ChrW(&h00e4))
        str1 += String.Format("Unicode characters. {0} {1} s {2}{3}",
                              ChrW(&h00a9), ChrW(&h010C), ChrW(&h0062), ChrW(&h0308))
        Console.WriteLine("Created original string...")
        Console.WriteLine()

        ' Convert string to byte array.                     
        Dim bytes() As Byte = enc.GetBytes(str1)

        Dim fs As FileStream = File.Create(".\characters.bin")
        Dim bw As New BinaryWriter(fs)
        bw.Write(bytes)
        bw.Close()

        ' Read bytes from file.
        Dim fsIn As FileStream = File.OpenRead(".\characters.bin")
        Dim br As New BinaryReader(fsIn)

        Const count As Integer = 10      ' Number of bytes to read at a time. 
        Dim bytesRead(9) As Byte         ' Buffer (byte array).
        Dim read As Integer              ' Number of bytes actually read. 
        Dim str2 As String = ""          ' Decoded string.

        ' Try using Encoding object for all operations.
        Do
            read = br.Read(bytesRead, 0, count)
            str2 += enc.GetString(bytesRead, 0, read)
        Loop 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)
        Dim decoder As Decoder = enc.GetDecoder()
        Dim chars(50) As Char
        Dim index As Integer = 0         ' Next character to write in array.
        Dim written As Integer = 0       ' Number of chars written to array.
        Do
            read = br.Read(bytesRead, 0, count)
            If index + decoder.GetCharCount(bytesRead, 0, read) - 1 >= chars.Length Then
                Array.Resize(chars, chars.Length + 50)
            End If
            written = decoder.GetChars(bytesRead, 0, read, chars, index)
            index += written
        Loop While read = count
        br.Close()
        ' Instantiate a string with the decoded characters.
        Dim str3 As New String(chars, 0, index)
        Console.WriteLine("Decoded string using UnicodeEncoding.Decoder.GetString()...")
        CompareForEquality(str1, str3)
    End Sub

    Private Sub CompareForEquality(original As String, decoded As String)
        Dim result As Boolean = original.Equals(decoded)
        Console.WriteLine("original = decoded: {0}",
                          original.Equals(decoded, StringComparison.Ordinal))
        If Not result Then
            Console.WriteLine("Code points in original string:")
            For Each ch In original
                Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
            Next
            Console.WriteLine()

            Console.WriteLine("Code points in decoded string:")
            For Each ch In decoded
                Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
            Next
            Console.WriteLine()
        End If
    End Sub
End Module
' 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

Выбор резервной стратегии

Когда метод пытается закодировать или декодировать символ, но сопоставление не существует, он должен реализовать резервную стратегию, которая определяет, как должно обрабатываться неудачное сопоставление. Существует три типа резервных стратегий:

  • Лучший резервный вариант

  • Резервный вариант замены

  • Резервный вариант исключения

Это важно

Наиболее распространенные проблемы в операциях кодирования возникают, когда символ Юникода нельзя сопоставить с определенной кодировкой кодовой страницы. Наиболее распространенные проблемы при декодировании операций возникают, когда недопустимые последовательности байтов нельзя преобразовать в допустимые символы Юникода. По этим причинам следует знать, какую резервную стратегию использует конкретный объект кодирования. По возможности следует указать резервную стратегию, используемую объектом кодирования при создании экземпляра объекта.

Наилучший резервный вариант

Если символ не имеет точного совпадения в целевой кодировке, кодировщик может попытаться сопоставить его с аналогичным символом. (Наиболее подходящая резервная замена — это в основном проблема кодирования, а не декодирования. Очень мало кодовых страниц содержат символы, которые не могут быть успешно сопоставлены с Юникодом.) Наиболее подходящая резервная замена является настройкой по умолчанию для кодовых страниц и кодировок двойных байтовых наборов символов, которые извлекаются перегрузками Encoding.GetEncoding(Int32) и Encoding.GetEncoding(String).

Примечание.

В теории классы кодировки Юникода, предоставляемые в .NET (UTF8Encoding, UnicodeEncoding, и UTF32Encoding), поддерживают каждый символ в каждом наборе символов, так что их можно использовать для устранения проблем с сопоставлением по наилучшему подходящему варианту.

Стратегии оптимального соответствия зависят от различных кодовых страниц. Например, для некоторых кодовых страниц полные латинские символы сопоставляются с более распространенными латинскими символами уменьшенной ширины. Для других кодовых страниц это сопоставление не осуществляется. Даже при агрессивной стратегии оптимального соответствия невозможно подобрать соответствие для некоторых символов в некоторых кодировках. Например, китайский идеограф не имеет разумного сопоставления с кодовой страницей 1252. В этом случае используется строка замены. По умолчанию эта строка представляет собой только один ЗНАК ВОПРОСА (U+003F).

Примечание.

Стратегии лучшего соответствия не документированы подробно. Однако на веб-сайте консорциума Юникода задокументировано несколько кодовых страниц. Просмотрите файлreadme.txt в этой папке, чтобы узнать, как интерпретировать файлы сопоставления.

В следующем примере используется кодовая страница 1252 (кодовая страница Windows для западноевропейских языков) для иллюстрации оптимального соответствия и его недостатков. Метод Encoding.GetEncoding(Int32) используется для получения объекта кодирования для кодовой страницы 1252. По умолчанию он использует наиболее подходящее сопоставление для символов Юникода, которые он не поддерживает. В примере создается экземпляр строки, содержащей три символа, отличных от ASCII: CIRCLED LATIN CAPITAL LETTER S (U+24C8), SUPERSCRIPT FIVE (U+2075) и INFINITY (U+221E) — разделенные пробелами. Как показано в выходных данных из примера, при кодировании строки три исходных символа, отличных от пробела, заменяются вопросительным знаком (U+003F), ЦИФРОЙ ПЯТЬ (U+0035) и ЦИФРОЙ ВОСЕМЬ (U+0038). ЦИФРА ВОСЕМЬ является особенно плохой заменой неподдерживаемого символа 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
Imports System.Text

Module Example
    Public Sub Main()
        ' Get an encoding for code page 1252 (Western Europe character set).
        Dim cp1252 As Encoding = Encoding.GetEncoding(1252)

        ' Define and display a string.
        Dim str As String = String.Format("{0} {1} {2}", ChrW(&h24c8), ChrW(&H2075), ChrW(&h221E))
        Console.WriteLine("Original string: " + str)
        Console.Write("Code points in string: ")
        For Each ch In str
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
        Next
        Console.WriteLine()
        Console.WriteLine()

        ' Encode a Unicode string.
        Dim bytes() As Byte = cp1252.GetBytes(str)
        Console.Write("Encoded bytes: ")
        For Each byt In bytes
            Console.Write("{0:X2} ", byt)
        Next
        Console.WriteLine()
        Console.WriteLine()

        ' Decode the string.
        Dim str2 As String = cp1252.GetString(bytes)
        Console.WriteLine("String round-tripped: {0}", str.Equals(str2))
        If Not str.Equals(str2) Then
            Console.WriteLine(str2)
            For Each ch In str2
                Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
            Next
        End If
    End Sub
End Module
' 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, который кодирует данные Юникода в данные кодовой страницы, и существуют устаревшие приложения, которые зависят от этого поведения. Однако большинство новых приложений должны избежать оптимального поведения по соображениям безопасности. Например, приложения не должны обрабатывать доменное имя на основе наиболее подходящей кодировки.

Примечание.

Кроме того, можно реализовать настраиваемое резервное сопоставление для кодирования. Дополнительные сведения см. в разделе «Реализация пользовательской резервной стратегии».

Если резервный вариант используется по умолчанию для объекта кодирования, можно выбрать другую стратегию при получении объекта Encoding, вызвав перегрузку Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback) или Encoding.GetEncoding(String, EncoderFallback, DecoderFallback). В следующем разделе приведен пример, который заменяет каждый символ, который не может быть сопоставлен с кодовой страницей 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
Imports System.Text

Module Example
    Public Sub Main()
        Dim cp1252r As Encoding = Encoding.GetEncoding(1252,
                                           New EncoderReplacementFallback("*"),
                                           New DecoderReplacementFallback("*"))

        Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))
        Console.WriteLine(str1)
        For Each ch In str1
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
        Next
        Console.WriteLine()

        Dim bytes() As Byte = cp1252r.GetBytes(str1)
        Dim str2 As String = cp1252r.GetString(bytes)
        Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
        If Not str1.Equals(str2) Then
            Console.WriteLine(str2)
            For Each ch In str2
                Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
            Next
            Console.WriteLine()
        End If
    End Sub
End Module
' The example displays the following output:
'       Ⓢ ⁵ ∞
'       24C8 0020 2075 0020 221E
'       Round-trip: False
'       * * *
'       002A 0020 002A 0020 002A

Резервная замена

Если символ не имеет точного соответствия в целевой схеме, но нет соответствующего символа, с которым его можно сопоставить, приложение может указать замещающий символ или строку. Это поведение по умолчанию для декодера Юникода, которое заменяет любую двухбайтовую последовательность, которую он не может декодировать с помощью REPLACEMENT_CHARACTER (U+FFFD). Это также поведение ASCIIEncoding класса по умолчанию, которое заменяет каждый символ, который не может кодировать или декодировать с помощью вопросительного знака. В следующем примере показана замена символов для строки Юникода из предыдущего примера. Как показано в выходных данных, каждый символ, который не может быть декодирован в байтовое значение 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
Imports System.Text

Module Example
    Public Sub Main()
        Dim enc As Encoding = Encoding.Ascii

        Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))
        Console.WriteLine(str1)
        For Each ch In str1
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
        Next
        Console.WriteLine()
        Console.WriteLine()

        ' Encode the original string using the ASCII encoder.
        Dim bytes() As Byte = enc.GetBytes(str1)
        Console.Write("Encoded bytes: ")
        For Each byt In bytes
            Console.Write("{0:X2} ", byt)
        Next
        Console.WriteLine()
        Console.WriteLine()

        ' Decode the ASCII bytes.
        Dim str2 As String = enc.GetString(bytes)
        Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
        If Not str1.Equals(str2) Then
            Console.WriteLine(str2)
            For Each ch In str2
                Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
            Next
            Console.WriteLine()
        End If
    End Sub
End Module
' 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 включает классы EncoderReplacementFallback и DecoderReplacementFallback, которые заменяют замещающую строку, если символ не соответствует в операции кодирования или декодирования. По умолчанию эта строка замены представляет собой вопросительный знак, но можно вызвать перегрузку конструктора классов, чтобы выбрать другую строку. Как правило, строка замены является одним символом, хотя это не является обязательным. В следующем примере изменяется поведение кодировщика 1252 путем создания экземпляра объекта EncoderReplacementFallback, который использует звездочку (*) в качестве строки замены.

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
Imports System.Text

Module Example
    Public Sub Main()
        Dim cp1252r As Encoding = Encoding.GetEncoding(1252,
                                           New EncoderReplacementFallback("*"),
                                           New DecoderReplacementFallback("*"))

        Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))
        Console.WriteLine(str1)
        For Each ch In str1
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
        Next
        Console.WriteLine()

        Dim bytes() As Byte = cp1252r.GetBytes(str1)
        Dim str2 As String = cp1252r.GetString(bytes)
        Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
        If Not str1.Equals(str2) Then
            Console.WriteLine(str2)
            For Each ch In str2
                Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
            Next
            Console.WriteLine()
        End If
    End Sub
End Module
' The example displays the following output:
'       Ⓢ ⁵ ∞
'       24C8 0020 2075 0020 221E
'       Round-trip: False
'       * * *
'       002A 0020 002A 0020 002A

Примечание.

Вы также можете создать класс для замены кодирования. Дополнительные сведения см. в разделе «Реализация пользовательской резервной стратегии».

Помимо ВОПРОСИТЕЛЬНОГО ЗНАКА (U+003F), символ замены Юникода (U+FFFD) часто используется в качестве замены, особенно при декодировании последовательностей байтов, которые невозможно успешно перевести в символы Юникода. Однако вы можете выбрать любую строку замены, и она может содержать несколько символов.

Резервный вариант исключения

Вместо предоставления наиболее подходящей резервной замены или строки замены, кодировщик может вызвать 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.
Imports System.Text

Module Example
    Public Sub Main()
        Dim enc As Encoding = Encoding.GetEncoding("us-ascii",
                                                   New EncoderExceptionFallback(),
                                                   New DecoderExceptionFallback())

        Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))
        Console.WriteLine(str1)
        For Each ch In str1
            Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
        Next
        Console.WriteLine()
        Console.WriteLine()

        ' Encode the original string using the ASCII encoder.
        Dim bytes() As Byte = {}
        Try
            bytes = enc.GetBytes(str1)
            Console.Write("Encoded bytes: ")
            For Each byt In bytes
                Console.Write("{0:X2} ", byt)
            Next
            Console.WriteLine()
        Catch e As EncoderFallbackException
            Console.Write("Exception: ")
            If e.IsUnknownSurrogate() Then
                Console.WriteLine("Unable to encode surrogate pair 0x{0:X4} 0x{1:X3} at index {2}.",
                                  Convert.ToUInt16(e.CharUnknownHigh),
                                  Convert.ToUInt16(e.CharUnknownLow),
                                  e.Index)
            Else
                Console.WriteLine("Unable to encode 0x{0:X4} at index {1}.",
                                  Convert.ToUInt16(e.CharUnknown),
                                  e.Index)
            End If
            Exit Sub
        End Try
        Console.WriteLine()

        ' Decode the ASCII bytes.
        Try
            Dim str2 As String = enc.GetString(bytes)
            Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
            If Not str1.Equals(str2) Then
                Console.WriteLine(str2)
                For Each ch In str2
                    Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
                Next
                Console.WriteLine()
            End If
        Catch e As DecoderFallbackException
            Console.Write("Unable to decode byte(s) ")
            For Each unknown As Byte In e.BytesUnknown
                Console.Write("0x{0:X2} ")
            Next
            Console.WriteLine("at index {0}", e.Index)
        End Try
    End Sub
End Module
' The example displays the following output:
'       Ⓢ ⁵ ∞
'       24C8 0020 2075 0020 221E
'       
'       Exception: Unable to encode 0x24C8 at index 0.

Примечание.

Вы также можете реализовать настраиваемый обработчик исключений для операции кодирования. Дополнительные сведения см. в разделе «Реализация пользовательской резервной стратегии».

Объекты EncoderFallbackException и DecoderFallbackException предоставляют следующие сведения о условии, вызвавшем исключение:

  • Объект EncoderFallbackException имеет метод IsUnknownSurrogate, указывающий, представляют ли символ или символы, которые не могут быть закодированы, неизвестную суррогатную пару (в этом случае метод возвращает true) или неизвестный одиночный символ (в этом случае метод возвращает false). Символы в суррогатной паре доступны из свойств EncoderFallbackException.CharUnknownHigh и EncoderFallbackException.CharUnknownLow. Неизвестный единичный символ доступен из свойства EncoderFallbackException.CharUnknown. Свойство EncoderFallbackException.Index указывает позицию в строке, в которой был найден первый символ, который не удалось закодировать.

  • Объект DecoderFallbackException содержит BytesUnknown свойство, возвращающее массив байтов, которые не могут быть декодированы. Свойство DecoderFallbackException.Index указывает начальную позицию неизвестных байтов.

Хотя объекты EncoderFallbackException и DecoderFallbackException предоставляют достаточные диагностические сведения об исключении, они не предоставляют доступ к буферу кодирования и буферу декодирования. Поэтому они не позволяют заменять или исправлять недопустимые данные в методе кодирования или декодирования.

Реализация настраиваемой резервной стратегии

Помимо оптимального сопоставления, реализуемого внутри кодовых страниц, .NET включает в себя следующие классы для реализации резервной стратегии:

Кроме того, можно реализовать пользовательское решение, которое использует резервное соответствие, резервное значение замены или резервное значение по умолчанию для исключений, выполнив следующие действия:

  1. Создайте класс на основе EncoderFallback для операций кодирования и на основе DecoderFallback для операций декодирования.

  2. Создайте класс на основе EncoderFallbackBuffer для операций кодирования и на основе DecoderFallbackBuffer для операций декодирования.

  3. Для резервного использования исключений, если предопределенные классы EncoderFallbackException и DecoderFallbackException не соответствуют вашим потребностям, создайте класс на основе объекта исключения, например, Exception или ArgumentException.

Производный от EncoderFallback или DecoderFallback

Чтобы реализовать пользовательское резервное решение, необходимо создать класс, наследующий от EncoderFallback для операций кодирования и от DecoderFallback для операций декодирования. Экземпляры этих классов передаются Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) методу и служат посредником между классом кодирования и резервной реализацией.

При создании пользовательского резервного решения для кодировщика или декодера необходимо реализовать следующие элементы:

  • Свойство EncoderFallback.MaxCharCount или DecoderFallback.MaxCharCount, которое возвращает максимальное возможное количество символов, которые механизм резервного восстановления (лучшее соответствие, замена или исключение) может вернуть для замены одного символа. Для пользовательского исключения в качестве резервного механизма его значение равно нулю.

  • Метод EncoderFallback.CreateFallbackBuffer или DecoderFallback.CreateFallbackBuffer, который возвращает вашу пользовательскую реализацию EncoderFallbackBuffer или DecoderFallbackBuffer. Метод вызывается кодировщиком при обнаружении первого символа, который не может успешно закодировать, или декодировщиком при обнаружении первого байта, который не может успешно декодировать.

Производный от EncoderFallbackBuffer или DecoderFallbackBuffer

Чтобы реализовать пользовательское резервное решение, необходимо также создать класс, наследующий от EncoderFallbackBuffer для операций кодирования и от DecoderFallbackBuffer для операций декодирования. Экземпляры этих классов возвращаются методом CreateFallbackBuffer, принадлежащим классам EncoderFallback и DecoderFallback. Метод EncoderFallback.CreateFallbackBuffer вызывается кодировщиком при обнаружении первого символа, который не может кодироваться, и DecoderFallback.CreateFallbackBuffer метод вызывается декодером при обнаружении одного или нескольких байтов, которые он не может декодировать. Классы EncoderFallbackBuffer и DecoderFallbackBuffer предоставляют резервную реализацию. Каждый экземпляр представляет буфер, содержащий резервные символы, заменяющие символы, которые не могут быть закодированы или последовательности байтов, которые нельзя декодировать.

При создании пользовательского резервного решения для кодировщика или декодера необходимо реализовать следующие элементы:

  • Метод EncoderFallbackBuffer.Fallback или DecoderFallbackBuffer.Fallback. EncoderFallbackBuffer.Fallback вызывается кодировщиком для предоставления резервного буфера с информацией о символе, который он не может кодировать. Так как символ, который необходимо закодировать, может быть суррогатной парой, этот метод перегружен. Одна перегрузка передает символ, закодированный и его индекс в строке. Вторая перегрузка передается высоким и низким суррогатом вместе со своим индексом в строке. Метод DecoderFallbackBuffer.Fallback вызывается декодером, чтобы предоставить резервный буфер с информацией о байтах, которые он не может декодировать. Этот метод передает массив байтов, которые он не может декодировать, а также индекс первого байта. Резервный метод должен возвращать true , если резервный буфер может предоставить подходящий или замещающий символ или символы; в противном случае он должен вернуться false. Для резервного механизма исключения, метод резервирования должен выбросить исключение.

  • Метод EncoderFallbackBuffer.GetNextChar или метод DecoderFallbackBuffer.GetNextChar, который повторно вызывается кодировщиком или декодером, чтобы получить следующий символ из резервного буфера. При возврате всех резервных символов метод должен возвращать U+0000.

  • Свойство EncoderFallbackBuffer.Remaining или DecoderFallbackBuffer.Remaining, которое возвращает количество символов, оставшихся в резервном буфере.

  • Метод EncoderFallbackBuffer.MovePrevious или DecoderFallbackBuffer.MovePrevious, который перемещает текущую позицию в резервном буфере на предыдущий символ.

  • Метод EncoderFallbackBuffer.Reset или DecoderFallbackBuffer.Reset, который повторно инициализирует резервный буфер.

Если резервная реализация является оптимальным резервным вариантом или резервным вариантом замены, классы, производные от EncoderFallbackBuffer и DecoderFallbackBuffer, также содержат два закрытых поля экземпляра: точное количество символов в буфере и индекс следующего символа в буфере для возврата.

Пример использования EncoderFallback

В предыдущем примере используется резервный вариант замены для замены символов Юникода, которые не соответствуют символам ASCII звездочкой (*). В следующем примере используется настраиваемая резервная реализация для лучшего сопоставления символов, отличных от ASCII.

Следующий код определяет класс с именем CustomMapper, производный от EncoderFallback, для обработки наилучшего сопоставления символов, отличных от ASCII. Его CreateFallbackBuffer метод возвращает CustomMapperFallbackBuffer объект, который предоставляет реализацию EncoderFallbackBuffer . Класс CustomMapper использует Dictionary<TKey,TValue> объект для хранения сопоставлений неподдерживаемых символов Юникода (значения ключа) и соответствующих 8-разрядных символов (которые хранятся в двух последовательных байтах в 64-разрядном целочисленном значении). Чтобы сделать это сопоставление доступным для резервного буфера, CustomMapper экземпляр передается в качестве параметра конструктору CustomMapperFallbackBuffer классов. Так как самое длинное сопоставление — это строка INF для символа Юникода U+221E, 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; }
   }
}
Public Class CustomMapper : Inherits EncoderFallback
    Public DefaultString As String
    Friend mapping As Dictionary(Of UShort, ULong)

    Public Sub New()
        Me.New("?")
    End Sub

    Public Sub New(ByVal defaultString As String)
        Me.DefaultString = defaultString

        ' Create table of mappings
        mapping = New Dictionary(Of UShort, ULong)
        mapping.Add(&H24C8, &H53)
        mapping.Add(&H2075, &H35)
        mapping.Add(&H221E, &H49004E0046)
    End Sub

    Public Overrides Function CreateFallbackBuffer() As System.Text.EncoderFallbackBuffer
        Return New CustomMapperFallbackBuffer(Me)
    End Function

    Public Overrides ReadOnly Property MaxCharCount As Integer
        Get
            Return 3
        End Get
    End Property
End Class

Следующий код определяет класс, производный CustomMapperFallbackBuffer от EncoderFallbackBuffer. Словарь, содержащий оптимальные сопоставления и определенный в экземпляре CustomMapper, доступен из конструктора его класса. Его Fallback метод возвращает true , если любой из символов Юникода, которые кодировщик ASCII не может кодировать, определен в словаре сопоставления; в противном случае возвращается 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;
   }
}
Public Class CustomMapperFallbackBuffer : Inherits EncoderFallbackBuffer

    Dim count As Integer = -1        ' Number of characters to return
    Dim index As Integer = -1        ' Index of character to return
    Dim fb As CustomMapper
    Dim charsToReturn As String

    Public Sub New(ByVal fallback As CustomMapper)
        MyBase.New()
        Me.fb = fallback
    End Sub

    Public Overloads Overrides Function Fallback(ByVal charUnknownHigh As Char, ByVal charUnknownLow As Char, ByVal index As Integer) As Boolean
        ' Do not try to map surrogates to ASCII.
        Return False
    End Function

    Public Overloads Overrides Function Fallback(ByVal charUnknown As Char, ByVal index As Integer) As Boolean
        ' Return false if there are already characters to map.
        If count >= 1 Then Return False

        ' Determine number of characters to return.
        charsToReturn = String.Empty

        Dim key As UShort = Convert.ToUInt16(charUnknown)
        If fb.mapping.ContainsKey(key) Then
            Dim bytes() As Byte = BitConverter.GetBytes(fb.mapping.Item(key))
            Dim ctr As Integer
            For Each byt In bytes
                If byt > 0 Then
                    ctr += 1
                    charsToReturn += Chr(byt)
                End If
            Next
            count = ctr
        Else
            ' Return default.
            charsToReturn = fb.DefaultString
            count = 1
        End If
        Me.index = charsToReturn.Length - 1

        Return True
    End Function

    Public Overrides Function GetNextChar() As Char
        ' We'll return a character if possible, so subtract from the count of chars to return.
        count -= 1
        ' If count is less than zero, we've returned all characters.
        If count < 0 Then Return ChrW(0)

        Me.index -= 1
        Return charsToReturn(Me.index + 1)
    End Function

    Public Overrides Function MovePrevious() As Boolean
        ' Original: if count >= -1 and pos >= 0
        If count >= -1 Then
            count += 1
            Return True
        Else
            Return False
        End If
    End Function

    Public Overrides ReadOnly Property Remaining As Integer
        Get
            Return If(count < 0, 0, count)
        End Get
    End Property

    Public Overrides Sub Reset()
        count = -1
        index = -1
    End Sub
End Class

Затем следующий код создает объект 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();
      }
   }
}
Imports System.Text
Imports System.Collections.Generic

Module Module1

    Sub Main()
        Dim enc As Encoding = Encoding.GetEncoding("us-ascii", New CustomMapper(), New DecoderExceptionFallback())

        Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&H24C8), ChrW(&H2075), ChrW(&H221E))
        Console.WriteLine(str1)
        For ctr As Integer = 0 To str1.Length - 1
            Console.Write("{0} ", Convert.ToUInt16(str1(ctr)).ToString("X4"))
            If ctr = str1.Length - 1 Then Console.WriteLine()
        Next
        Console.WriteLine()

        ' Encode the original string using the ASCII encoder.
        Dim bytes() As Byte = enc.GetBytes(str1)
        Console.Write("Encoded bytes: ")
        For Each byt In bytes
            Console.Write("{0:X2} ", byt)
        Next
        Console.WriteLine()
        Console.WriteLine()

        ' Decode the ASCII bytes.
        Dim str2 As String = enc.GetString(bytes)
        Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
        If Not str1.Equals(str2) Then
            Console.WriteLine(str2)
            For Each ch In str2
                Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
            Next
            Console.WriteLine()
        End If
    End Sub
End Module

См. также