Кодировки в .NET Framework
Символы — это абстрактные сущности, которые могут представляться множеством различных способов. Кодировка — это система, где с каждым символом поддерживаемого набора символов сопоставляется значение, представляющее этот символ. Например, азбука Морзе — это кодировка, в которой каждому символу латинского алфавита соответствует набор точек и тире, которые можно передавать с помощью телеграфа. Компьютерная кодировка — это система, где с каждым символом поддерживаемого набора символов сопоставлено числовое значение, представляющее этот символ. Кодировка состоит из двух компонентов:
Кодировщик, преобразующий последовательность символов в последовательность числовых значений (байтов).
Декодер, преобразующий последовательность байтов в последовательность символов.
Кодировка описывает набор правил, в соответствии с которыми работают кодировщик и декодер. Например, класс UTF8Encoding описывает правила кодирования и декодирования для формата UTF-8, в котором используется от одного до четырех байтов для представления одного символа Юникода. В процессе кодирования и декодирования также может выполняться проверка. Например, класс UnicodeEncoding предназначен для проверки допустимости пар, составляемых всеми символами-заместителями. (Пара символов-заместителей состоит из символа с кодовой точкой в диапазоне от U+D800 до U+DBFF и символа с кодовой точкой в диапазоне от U+DC00 до U+DFFF.) Резервная стратегия определяет, как кодировщик обрабатывает недопустимые символы, и то, как декодер обрабатывает недопустимые байты.
Внимание |
---|
Классы кодировок .NET Framework позволяют хранить и преобразовывать данные символов.Их не следует использовать для хранения двоичных данных в строковом виде.В зависимости от используемой кодировки преобразование двоичных данных в строковый формат с использованием классов кодировок может привести к неожиданному результату и неточным или поврежденным данным.Для преобразования двоичных данных в строковую форму используйте метод Convert.ToBase64String(Byte>). |
В приложениях, ориентированных на среду CLR, кодировщики используются для сопоставления представлений символов Юникода, поддерживаемого средой CLR, с другими схемами кодирования. Декодеры используются для сопоставления символов различных кодировок с Юникодом.
Этот раздел состоит из следующих подразделов.
Кодировки в .NET Framework
Все классы кодировок в .NET Framework наследуются от класса System.Text.Encoding — абстрактного класса, определяющего общую для всех кодировок функциональность. Для обращения к отдельным объектам кодировок, реализованным в .NET Framework, можно выполнить следующие действия.
Использовать статические свойства класса Encoding, возвращающие объекты, представляющие стандартные кодировки, доступные в .NET Framework (ASCII, UTF-7, UTF-8, UTF-16 и UTF-32). Например, свойство Encoding.Unicode возвращает объект UnicodeEncoding. Каждый объект использует резервную стратегию замены для обработки строк, которые он не может закодировать, и байтов, которые не может декодировать. (Дополнительные сведения см. в подразделе Стратегия замены.)
Вызвать конструктор класса кодировки. Таким образом могут быть созданы объекты для кодировок ASCII, UTF-7, UTF-8, UTF-16 и UTF-32. По умолчанию каждый объект использует резервную стратегию замены для обработки строк, которые он не может закодировать, и байтов, которые он не может декодировать, но можно задать, чтобы вместо этого создавалось исключение. (Дополнительные сведения см. в подразделах Стратегия замены и Стратегия исключения.)
Вызвать конструктор Encoding.Encoding(Int32) и передать ему целое число, представляющее кодировку. Объекты стандартных кодировок используют резервные стратегии замены, а объекты кодовой страницы и кодировки двухбайтовых символов (DBCS) используют резервную стратегию наилучшего соответствия для обработки строк, которые не удается закодировать, или байтов, которые не удается декодировать. (Дополнительные сведения см. в подразделе Стратегия наилучшего соответствия.)
Вызвать метод Encoding.GetEncoding, возвращающий любую стандартную кодировку, кодовую страницу или кодировку DBCS, доступную в .NET Framework. Перегрузки позволяют задать объект как для кодировщика, так и для декодера.
Примечание |
---|
В стандарте Юникода каждому символу в каждом поддерживаемом символьном наборе присваивается кодовая точка (номер) и имя.Например, символ "A" представляется кодовой точкой U+0041 и именем "LATIN CAPITAL LETTER A".Кодировки UTF определяют способы кодирования кодовой точки в виде последовательности из одного или более байтов.Схема кодировки Юникод упрощает разработку международных приложений, так как позволяет представлять символы любых наборов символов в единой кодировке.Разработчикам приложений больше не нужно сохранять данные о типе кодировки, которая была использована для представления символов конкретного языка или системы записи; передача данных между системами, использующими различные языки, может происходить без искажений. Платформа .NET Framework поддерживает три кодировки, определенные стандартом Юникод: UTF-8, UTF-16 и UTF-32.Дополнительные сведения о символах Юникод см. в описании стандарта на домашней странице Юникода. |
Сведения обо всех доступных в .NET Framework кодировках можно получить, вызвав метод Encoding.GetEncodings. Платформа .NET Framework поддерживает системы кодирования символов, перечисленные в следующей таблице.
Кодировка |
Класс |
Описание |
Преимущества / недостатки |
---|---|---|---|
ASCII |
[ T:System.Text.ASCIIEncoding ] |
Кодирует ограниченный диапазон символов, используя семь нижних битов байта. |
Так как эта кодировка поддерживает только значения символов от U+0000 до U+007F, то во многих случаях она не отвечает требованиям международных приложений. |
UTF-7 |
[ T:System.Text.UTF7Encoding ] |
Представляет символы в виде последовательностей 7-разрядных символов ASCII. Символы, отличные от ASCII Юникода, представлены в виде escape-последовательности символов ASCII. |
UTF-7 поддерживает протоколы, например протоколы электронной почты и групп новостей. Однако формат UTF-7 недостаточно безопасен или надежен. В некоторых случаях изменение одного бита может привести к существенному изменению интерпретации всей строки UTF-7. В других случаях для кодировки одного и того же текста могут использоваться разные строки UTF-7. В последовательностях, содержащих отличные от ASCII символы, формат UTF-7 требует больше пространства, чем UTF-8, и процесс кодирования/декодирования выполняется медленнее. Следовательно, при возможности лучше использовать UTF-8 вместо UTF-7. |
UTF-8 |
[ T:System.Text.UTF8Encoding ] |
Представляет каждую кодовую точку Юникода в виде последовательности от одного до четырех байтов. |
UTF-8 поддерживает 8-разрядный размер данных и хорошо работает со многими операционными системами. Для диапазона символов ASCII UTF-8 идентичен кодировке ASCII и представляет более широкий набор символов. Однако для китайской, японской, корейской письменности UTF-8 может потребовать три байта для каждого символа, создавая, таким образом, данные большего размера, чем UTF-16. Обратите внимание, что иногда увеличение размера для китайских, японских и корейских языков объясняется объемом данных ASCII, например тегами HTML. |
UTF-16 |
[ T:System.Text.UnicodeEncoding ] |
Представляет каждую кодовую точку Юникода в виде последовательности из одного или двух 16-разрядных целых чисел. Наиболее распространены символы Юникода требуют только одной кодовой точки UTF-16, хотя дополнительные символы Юникода (U+10000 и далее) требуют двух замещающих кодовых точек UTF-16. Поддерживается как прямой порядок байтов, так и обратный порядок байтов. |
Кодировка UTF-16 используется средой CLR для представления значений Char и String, также она используется ОС Windows для представления значений WCHAR. |
UTF-32 |
[ T:System.Text.UTF32Encoding ] |
Представляет каждую кодовую точку Юникода в виде 32-разрядного целого числа. Поддерживается как прямой порядок байтов, так и обратный порядок байтов. |
Кодировка UTF-32 используется в случае, когда приложению требуется избежать поведения замещающей кодовой точки кодировки UTF-16 в операционных системах, где закодированное пространство имеет большое значение. Для кодирования отдельных отображаемых глифов может использоваться несколько символов UTF-32. |
Кодировки ANSI и ISO |
Предоставляет поддержку ряда кодовых страниц. В операционных системах Windows кодовые страницы используются для поддержки конкретного языка или группы языков. Таблицу, где перечислены кодовые страницы, поддерживаемые платформой .NET Framework, см. в описании класса 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, первый байт символа DBCS (старший байт) обрабатывается в сочетании со вторым байтом, следующим непосредственно за ним. Поскольку одна пара двухбайтовых кодовых точек может представлять различные символы в зависимости от кодовой страницы, эта схема также не позволяет использовать в одном потоке данных два языка, например японский и китайский. |
Эти кодировки позволяют работать с символами Юникода, а также с кодировками, наиболее часто используемыми в приложениях прежних версий. Кроме того, можно создать пользовательскую кодировку, определив класс, наследующий от класса Encoding, и переопределив его члены.
Выбор класса кодировки Encoding
Если у вас есть возможность выбрать кодировку для использования в приложении, следует использовать Юникод, предпочтительно UTF8Encoding или UnicodeEncoding. (Платформа .NET Framework также поддерживает третью кодировку Юникод, 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) для кодирования символов строки.
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.
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(" {0}", 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("{0}", ShowByteValues(bytes, index));
Console.WriteLine();
// Decode Unicode byte array to a string.
string newString = asciiEncoding.GetString(bytes, 0, index);
Console.WriteLine("Decoded: {0}", 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) для декодирования массива байтов.
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.
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: {0}", 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, спроектированы для работы с полным набором данных. Это значит, все данные, предназначенные для кодирования и декодирования, используются при одном вызове метода. Однако в некоторых случаях данные предоставляются в потоке, тогда данные для кодирования и декодирования можно получить только с помощью нескольких операций чтения. В таком случае необходимо, чтобы операция кодирования или декодирования "помнила" сохраненное после предыдущего вызова состояние. Методы классов, унаследованных от класса Encoder and Decoder, могут обрабатывать операции кодирования и декодирования, объединяющие несколько вызовов методов.
Объект Encoder для конкретной кодировки доступен в свойстве кодировки Encoding.GetEncoder. Объект Decoder для конкретной кодировки доступен в свойстве кодировки Encoding.GetDecoder. Для операций декодирования обратите внимание, что классы, унаследованные от класса Decoder, включают метод Decoder.GetChars, но не имеют метода, соответствующего методу Encoding.GetString.
В следующем примере демонстрируется разница между использованием методов Encoding.GetChars и Decoder.GetChars для декодирования массива байтов в Юникоде. В этом примере строка, содержащая несколько символов Юникода, кодируется в файл, а затем два метода декодирования используются для декодирования по десять байтов за раз. Поскольку суррогатная пара оказывается в десятом и одиннадцатом байтах, она декодируется другим вызовом метода. Как показывает вывод, метод Encoding.GetChars не может правильно декодировать байты и вместо этого заменяет их символом U+FFFD (ЗАМЕЩАЮЩИМ СИМВОЛОМ). А метод Decoder.GetChars может успешно декодировать массив байтов и получить исходную строку.
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
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: {0}",
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
Выбор стратегии перехода на запасные ресурсы
Когда метод пытается закодировать или декодировать символ, но не находит сопоставления, он должен использовать резервную стратегию, определяющую, как должно обрабатываться отсутствие сопоставления. Существует три вида резервных стратегий:
стратегия наилучшего соответствия;
стратегия замены;
стратегия исключения.
Важно |
---|
При операциях кодирования наиболее часто проблемы возникают, когда символ Юникода не удается сопоставить с определенной кодировкой кодовой страницы.При операциях декодирования наиболее часто проблемы возникают, когда недопустимую последовательность байтов не удается преобразовать в допустимые символы Юникода.Поэтому необходимо знать, какую резервную стратегию использует определенный объект кодировки.При возможности необходимо задавать резервную стратегию для объекта кодировки при создании объекта. |
Стратегия наилучшего соответствия
Если символ не имеет точного соответствия в целевой кодировке, кодировщик может попытаться сопоставить его с похожим символом. (Стратегия наилучшего соответствия скорее связана с проблемами, возникающими при кодировании, чем при декодировании. Существует лишь небольшое число кодовых страниц, символы которых не могут быть сопоставлены с Юникодом.) Стратегия наилучшего соответствия является стратегией по умолчанию для кодовых страниц и двухбайтовых кодировок, извлекаемых перегрузками Encoding.GetEncoding(Int32) и Encoding.GetEncoding(String).
Примечание |
---|
Теоретически классы кодировки Юникод, доступные в .NET Framework (UTF8Encoding, UnicodeEncoding и UTF32Encoding), поддерживают все символы всех наборов символов, поэтому их можно использовать, чтобы избежать проблем со стратегией наилучшего соответствия. |
Стратегии наилучшего соответствия для разных кодовых страниц различаются. Не все стратегии подробно задокументированы. Например, для некоторых кодовых страниц полноширинные латинские символы сопоставляются с более распространенными полуширинными символами. Для других кодовых страниц такое сопоставление не выполняется. Даже в случае применения активной стратегии наилучшего соответствия для некоторых символов некоторых кодировок отсутствует возможное сопоставление. Например, для идеографических символов китайского алфавита отсутствуют корректные сопоставления с символами кодовой страницы 1252. В этом случае используются замещающие строки. По умолчанию в качестве замещающей строки используется ЗНАК ВОПРОСА (U+003F).
В следующем примере используется кодовая страница 1252 (кодовая страница Windows для западноевропейских языков) для иллюстрации стратегии наилучшего соответствия и ее недостатков. Метод Encoding.GetEncoding(Int32) используется для извлечения объекта кодировки для кодовой страницы 1252. По умолчанию для неподдерживаемых символов Юникода используется стратегия наилучшего соответствия. В примере создается строка, содержащая три символа, отсутствующих в ASCII — ПРОПИСНАЯ ЛАТИНСКАЯ БУКВА S В КРУЖКЕ (U+24C8), НАДСТРОЧНЫЙ ИНДЕКС 5 (U+2075) и БЕСКОНЕЧНОСТЬ (U+221E), —- разделенные пробелами. Как показывает вывод, когда строка кодируется, эти три символа заменяются ВОПРОСИТЕЛЬНЫМ ЗНАКОМ (U+003F), ЦИФРОЙ ПЯТЬ (U+0035) И ЦИФРОЙ ВОСЕМЬ (U+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
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: {0}", 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 применяется стратегия наилучшего соответствия, при которой осуществляется кодирование данных в формате Юникод в формат кодовой страницы. Ряд приложений предыдущих версий построен с учетом этой стратегии. Однако в целях безопасности в большинстве новых приложений не рекомендуется применять эту стратегию. Например, при вводе имени домена в приложении не следует использовать стратегию наилучшего соответствия.
Примечание |
---|
Можно также реализовать пользовательскую стратегию наилучшего соответствия для кодировки.Дополнительные сведения см. в подразделе Реализация пользовательской резервной стратегии. |
Если стратегия наилучшего соответствия является резервной стратегией по умолчанию для объекта кодировки, можно выбрать другую резервную стратегию при извлечении объекта Encoding с помощью вызова перегрузки Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback) или Encoding.GetEncoding(String, EncoderFallback, DecoderFallback). В следующем подразделе есть пример, где каждый символ, который не удается сопоставить с кодовой страницей 1252, заменяется звездочкой (*).
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
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: {0}", 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+FFFD). Также это является поведением по умолчанию для класса ASCIIEncoding, который заменяет любой символ, который не удается закодировать или декодировать, вопросительным знаком. В следующем примере иллюстрируется замена символов строки в формате Юникод из предыдущего примера. Как показывает вывод, каждый символ, который не удается декодировать в байтовое значение ASCII, заменяется кодом "0x3F" (код ASCII для вопросительного знака).
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
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: {0}", 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 Framework есть классы EncoderReplacementFallback и DecoderReplacementFallback, подставляющие замещающую строку, если не удается точно сопоставить символ при кодировании или декодировании. По умолчанию эта замещающая строка — вопросительный знак, но можно вызвать перегрузку конструктора класса, чтобы выбрать другую строку. Обычно замещающая строка состоит из одного символа, но это не является обязательным требованием. В следующем примере поведение кодировщика кодовой страницы 1252 изменяется путем создания объекта EncoderReplacementFallback, который использует звездочку (*) в качестве замещающей строки.
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
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: {0}", 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), в качестве замещающей строки часто используется ЗАМЕЩАЮЩИЙ СИМВОЛ (U+FFFD) Юникода, особенно при декодировании последовательностей байтов, которые не удается преобразовать в символы Юникода. Однако можно задать любую замещающую строку, в том числе из нескольких символов.
Стратегия исключения
Вместо подстановки наиболее подходящей или замещающей строки кодировщик может создавать исключение EncoderFallbackException, если не удается закодировать набор символов, а декодер может создавать исключение DecoderFallbackException, если не удается декодировать массив байтов. Чтобы создать исключение при выполнении операций кодирования или декодирования, объект EncoderExceptionFallback и объект DecoderExceptionFallback передаются методу Encoding.GetEncoding(String, EncoderFallback, DecoderFallback). В следующем примере иллюстрируется резервная стратегия исключения с классом ASCIIEncoding.
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.
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{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);
return;
}
Console.WriteLine();
// Decode the ASCII bytes.
try {
string str2 = enc.GetString(bytes);
Console.WriteLine("Round-trip: {0}", 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 {0}", e.Index);
}
}
}
// 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 Framework содержит следующие классы для реализации резервной стратегии:
Классы EncoderReplacementFallback и EncoderReplacementFallbackBuffer можно использовать для замены символов в операциях кодирования.
Классы DecoderReplacementFallback и DecoderReplacementFallbackBuffer можно использовать для замены символов в операциях декодирования.
Классы EncoderExceptionFallback и EncoderExceptionFallbackBuffer можно использовать для создания исключения EncoderFallbackException, когда символ не удается закодировать.
Классы DecoderExceptionFallback и DecoderExceptionFallbackBuffer можно использовать для создания исключения DecoderFallbackException, когда символ не удается декодировать.
Кроме того, можно реализовать пользовательское решение, использующее резервную стратегию наилучшего соответствия или стратегию исключения, выполнив следующие шаги:
Для операций кодирования следует наследовать класс от класса EncoderFallback, а для операций декодирования следует наследовать класс от класса DecoderFallback.
Для операций кодирования следует наследовать класс от класса EncoderFallbackBuffer, а для операций декодирования следует наследовать класс от класса DecoderFallbackBuffer.
Для задания резервной стратегии исключения, если классы 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 : 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
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; }
}
}
В следующем примере кода определяется класс CustomMapperFallbackBuffer, наследующий от EncoderFallbackBuffer. Словарь, содержащий сопоставление наилучшего соответствия и определенный в экземпляре класса CustomMapper, доступен из конструктора класса. Метод Fallback возвращает значение true, если какие-либо из символов Юникода, которые не может закодировать кодировщик ASCII, определены в словаре сопоставлений; в противном случае возвращается значение false. Для каждого резервного действия закрытая переменная count указывает число символов, которые осталось вернуть, а закрытая переменная index указывает позицию в буфере строки, значение charsToReturn, следующего символа, который требуется вернуть.
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
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.
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
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: {0}", 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();
}
}
}
См. также
Ссылки
Другие ресурсы
Журнал изменений
Дата |
Журнал |
Причина |
---|---|---|
Октябрь 2010 |
Значительно пересмотрено. |
Улучшение информации. |