Comparteix a través de


Uso de clases de codificación de caracteres en .NET

En este artículo se explica cómo usar las clases que .NET proporciona para codificar y descodificar texto mediante varios esquemas de codificación. En las instrucciones se supone que ha leído Introducción a la codificación de caracteres en .NET.

Codificadores y descodificadores

.NET proporciona clases de codificación que codifican y descodifican texto mediante varios sistemas de codificación. Por ejemplo, la UTF8Encoding clase describe las reglas para codificar y descodificar desde UTF-8. .NET usa codificación UTF-16 (representada por la UnicodeEncoding clase) para string instancias. Los codificadores y descodificadores están disponibles para otros esquemas de codificación.

La codificación y la descodificación también pueden incluir validación. Por ejemplo, la UnicodeEncoding clase comprueba todas las char instancias del intervalo suplente para asegurarse de que están en pares suplentes válidos. Una estrategia de reserva determina cómo un codificador controla caracteres no válidos o cómo un descodificador controla bytes no válidos.

Advertencia

Las clases de codificación de .NET proporcionan una manera de almacenar y convertir datos de caracteres. No deben usarse para almacenar datos binarios en forma de cadena. Dependiendo de la codificación usada, la conversión de datos binarios en formato de cadena con las clases de codificación puede introducir un comportamiento inesperado y producir datos incorrectos o dañados. Para convertir datos binarios en un formulario de cadena, use el Convert.ToBase64String método .

Todas las clases de codificación de caracteres de .NET heredan de la System.Text.Encoding clase , que es una clase abstracta que define la funcionalidad común a todas las codificaciones de caracteres. Para acceder a los objetos de codificación individuales implementados en .NET, haga lo siguiente:

  • Use las propiedades estáticas de la Encoding clase , que devuelven objetos que representan las codificaciones de caracteres estándar disponibles en .NET (ASCII, UTF-7, UTF-8, UTF-16 y UTF-32). Por ejemplo, la Encoding.Unicode propiedad devuelve un UnicodeEncoding objeto . Cada objeto utiliza el mecanismo de sustitución para manejar cadenas que no puede codificar y bytes que no puede decodificar. Para obtener más información, consulte Alternativa de reemplazo.

  • Llame al constructor de clase de la codificación. Los objetos para las codificaciones ASCII, UTF-7, UTF-8, UTF-16 y UTF-32 pueden instanciarse de esta manera. De forma predeterminada, cada objeto usa la reserva de reemplazo para controlar las cadenas que no puede codificar y bytes que no puede descodificar, pero puede especificar que se debe producir una excepción en su lugar. Para obtener más información, consulte Alternativa de reemplazo y Alternativa de excepción.

  • Llame al Encoding(Int32) constructor y páselo un entero que representa la codificación. Los objetos de codificación estándar usan reserva de reemplazo, y los objetos de codificación de página de códigos y juego de caracteres de doble byte (DBCS) usan la reserva más adecuada para controlar las cadenas que no pueden codificar y bytes que no pueden descodificar. Para obtener más información, consulte Alternativa más adecuada.

  • Llame al método Encoding.GetEncoding, que devuelve cualquier codificación estándar, página de códigos o DBCS disponible en .NET. Las sobrecargas permiten especificar un objeto de reserva para el codificador y el descodificador.

Puede recuperar información sobre todas las codificaciones disponibles en .NET llamando al Encoding.GetEncodings método . .NET admite los esquemas de codificación de caracteres enumerados en la tabla siguiente.

Clase de codificación Descripción
ASCII Codifica un intervalo limitado de caracteres mediante el uso de los siete bits inferiores de un byte. Dado que esta codificación solo admite valores de caracteres desde U+0000 a U+007F, en la mayoría de los casos es inadecuada para las aplicaciones internacionalizadas.
UTF-7 Representa caracteres como secuencias de caracteres ASCII de 7 bits. Los caracteres Unicode no ASCII se representan mediante una secuencia de escape de caracteres ASCII. UTF-7 admite protocolos como correo electrónico y grupo de noticias. Sin embargo, UTF-7 no es especialmente seguro ni sólido. En algunos casos, cambiar un bit puede modificar radicalmente la interpretación de una cadena UTF-7 completa. En otros casos, diferentes cadenas UTF-7 pueden codificar el mismo texto. Para las secuencias que incluyen caracteres no ASCII, UTF-7 requiere más espacio que UTF-8 y la codificación y descodificación es más lenta. Por lo tanto, debe usar UTF-8 en lugar de UTF-7 si es posible.
UTF-8 Representa cada punto de código Unicode como una secuencia de uno a cuatro bytes. UTF-8 admite tamaños de datos de 8 bits y funciona bien con muchos sistemas operativos existentes. Para el intervalo ASCII de caracteres, UTF-8 es idéntico a la codificación ASCII y permite un conjunto más amplio de caracteres. Sin embargo, para los guiones chino-japonés-coreano (CJK), UTF-8 puede requerir tres bytes para cada carácter y puede provocar tamaños de datos mayores que UTF-16. A veces, la cantidad de datos ASCII, como etiquetas HTML, justifica el aumento del tamaño del intervalo CJK.
UTF-16 Representa cada punto de código Unicode como una secuencia de uno o dos enteros de 16 bits. Los caracteres Unicode más comunes solo requieren un punto de código UTF-16, aunque los caracteres complementarios Unicode (U+10000 y versiones posteriores) requieren dos puntos de código suplenteS UTF-16. Se admiten las órdenes de bytes en formato little-endian y big-endian. El entorno de ejecución común usa la codificación UTF-16 para representar los valores Char y String, y el sistema operativo Windows la utiliza para representar los valores WCHAR.
UTF-32 Representa cada punto de código Unicode como un entero de 32 bits. Se admiten las órdenes de bytes en formato little-endian y big-endian. La codificación UTF-32 se usa cuando las aplicaciones quieren evitar el comportamiento de punto de código suplente de codificación UTF-16 en sistemas operativos para los que el espacio codificado es demasiado importante. Los glifos únicos representados en una pantalla pueden estar codificados con más de un carácter UTF-32.
Codificación ANSI/ISO Proporciona compatibilidad con una variedad de páginas de códigos. En los sistemas operativos Windows, las páginas de códigos se usan para admitir un idioma específico o un grupo de idiomas. Para obtener una tabla que muestre las páginas de códigos compatibles con .NET, consulte la Encoding clase . Puede recuperar un objeto de codificación para una página de códigos determinada llamando al Encoding.GetEncoding(Int32) método . Una página de códigos contiene 256 puntos de código y está basado en cero. En la mayoría de las páginas de códigos, los puntos de código 0 a 127 representan el juego de caracteres ASCII y los puntos de código 128 a 255 difieren significativamente entre las páginas de códigos. Por ejemplo, la página de códigos 1252 proporciona los caracteres para los sistemas de escritura latina, incluidos inglés, alemán y francés. Los últimos 128 puntos de código de la página de códigos 1252 contienen los caracteres acentuados. La página de códigos 1253 proporciona códigos de caracteres necesarios en el sistema de escritura griego. Los últimos 128 puntos de código de la página de códigos 1253 contienen los caracteres griegos. Como resultado, una aplicación que se basa en páginas de códigos ANSI no puede almacenar griego y alemán en la misma secuencia de texto a menos que incluya un identificador que indique la página de códigos a la que se hace referencia.
Codificaciones de conjunto de caracteres de doble byte (DBCS) Admite idiomas, como chino, japonés y coreano, que contienen más de 256 caracteres. En un DBCS, un par de puntos de código (un byte doble) representa cada carácter. La Encoding.IsSingleByte propiedad devuelve false para codificaciones DBCS. Puede recuperar un objeto de codificación para un DBCS determinado llamando al Encoding.GetEncoding(Int32) método . Cuando una aplicación maneja datos DBCS, el byte inicial de un carácter DBCS (conocido como byte principal) se procesa en combinación con el byte siguiente (byte final) que sigue inmediatamente. Dado que un único par de puntos de código de doble byte puede representar caracteres diferentes en función de la página de códigos, este esquema todavía no permite la combinación de dos idiomas, como japonés y chino, en el mismo flujo de datos.

Estas codificaciones permiten trabajar con caracteres Unicode, así como con codificaciones que se usan con más frecuencia en aplicaciones heredadas. Además, puede crear una codificación personalizada definiendo una clase que derive de Encoding e invalide sus miembros.

Compatibilidad con la codificación de .NET Core

De forma predeterminada, .NET Core no ofrece codificaciones de página de códigos distintas de la página de códigos 28591 y las codificaciones Unicode, como UTF-8 y UTF-16. Sin embargo, puede agregar las codificaciones de página de códigos que se encuentran en aplicaciones estándar de Windows destinadas a .NET a la aplicación. Para obtener más información, consulte el CodePagesEncodingProvider tema.

Selección de una clase de codificación

Si tiene la oportunidad de elegir la codificación que usará la aplicación, debe usar una codificación Unicode, preferiblemente UTF8Encoding o UnicodeEncoding. (.NET también admite una tercera codificación Unicode, UTF32Encoding.)

Si planea usar una codificación ASCII (ASCIIEncoding), elija UTF8Encoding en su lugar. Las dos codificaciones son idénticas para el juego de caracteres ASCII, pero UTF8Encoding tiene las siguientes ventajas:

  • Puede representar cada carácter Unicode, mientras que ASCIIEncoding solo admite los valores de caracteres Unicode entre U+0000 y U+007F.

  • Proporciona detección de errores y una mejor seguridad.

  • Se ha ajustado para ser lo más rápido posible y debe ser más rápido que cualquier otra codificación. Incluso para el contenido que es completamente ASCII, las operaciones realizadas con UTF8Encoding son más rápidas que las operaciones realizadas con ASCIIEncoding.

Considere la posibilidad de usar ASCIIEncoding solo para aplicaciones heredadas. Sin embargo, incluso para las aplicaciones heredadas, UTF8Encoding puede ser una mejor opción por los siguientes motivos (suponiendo la configuración predeterminada):

  • Si la aplicación tiene contenido que no es estrictamente ASCII y lo codifica con ASCIIEncoding, cada carácter no ASCII codifica como signo de interrogación (?). Si la aplicación descodifica estos datos, se pierde la información.

  • Si la aplicación tiene contenido que no es estrictamente ASCII y lo codifica con UTF8Encoding, el resultado parece inintelligible si se interpreta como ASCII. Sin embargo, si la aplicación usa un descodificador UTF-8 para descodificar estos datos, los datos realizan un recorrido de ida y vuelta correctamente.

En una aplicación web, los caracteres enviados al cliente en respuesta a una solicitud web deben reflejar la codificación usada en el cliente. En la mayoría de los casos, debe establecer la propiedad HttpResponse.ContentEncoding al valor que devuelve la propiedad HttpRequest.ContentEncoding para mostrar el texto con la codificación que el usuario espera.

Usar un objeto de codificación

Un codificador convierte una cadena de caracteres (normalmente, caracteres Unicode) en su equivalente numérico (byte). Por ejemplo, puede usar un codificador ASCII para convertir caracteres Unicode en ASCII para que se puedan mostrar en la consola. Para realizar la conversión, llame al método Encoding.GetBytes. Si desea determinar cuántos bytes se necesitan para almacenar los caracteres codificados antes de realizar la codificación, puede llamar al GetByteCount método .

En el ejemplo siguiente se usa una única matriz de bytes para codificar cadenas en dos operaciones independientes. Mantiene un índice que indica la posición inicial en la matriz de bytes para el siguiente conjunto de bytes codificados en ASCII. Llama al ASCIIEncoding.GetByteCount(String) método para asegurarse de que la matriz de bytes es lo suficientemente grande como para dar cabida a la cadena codificada. A continuación, llama al ASCIIEncoding.GetBytes(String, Int32, Int32, Byte[], Int32) método para codificar los caracteres de la cadena.

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.

Un descodificador convierte una matriz de bytes que refleja una codificación de caracteres determinada en un conjunto de caracteres, ya sea en una matriz de caracteres o en una cadena. Para descodificar una matriz de bytes en una matriz de caracteres, llame al Encoding.GetChars método . Para descodificar una matriz de bytes en una cadena, llame al GetString método . Si desea determinar cuántos caracteres son necesarios para almacenar los bytes descodificados antes de realizar la descodificación, puede llamar al GetCharCount método .

En el ejemplo siguiente se codifican tres cadenas y, a continuación, se descodifican en una sola matriz de caracteres. Mantiene un índice que indica la posición inicial en la matriz de caracteres para el siguiente conjunto de caracteres descodificados. Llama al GetCharCount método para asegurarse de que la matriz de caracteres es lo suficientemente grande como para acomodar todos los caracteres descodificados. A continuación, llama al ASCIIEncoding.GetChars(Byte[], Int32, Int32, Char[], Int32) método para descodificar la matriz de bytes.

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.

Los métodos de codificación y descodificación de una clase derivada Encoding de están diseñados para funcionar en un conjunto completo de datos; es decir, todos los datos que se van a codificar o descodificar se proporcionan en una sola llamada al método. Sin embargo, en algunos casos, los datos están disponibles en una secuencia y los datos que se van a codificar o descodificar solo pueden estar disponibles desde operaciones de lectura independientes. Esto requiere la operación de codificación o descodificación para recordar cualquier estado guardado de su invocación anterior. Los métodos de las clases derivadas de Encoder y Decoder pueden controlar las operaciones de codificación y descodificación que abarcan varias llamadas a métodos.

Un Encoder objeto para una codificación determinada está disponible en la propiedad de Encoding.GetEncoder() esa codificación. Un Decoder objeto para una codificación determinada está disponible en la propiedad de Encoding.GetDecoder() esa codificación. Para las operaciones de descodificación, tenga en cuenta que las clases derivadas de Decoder incluyen un Decoder.GetChars método, pero no tienen un método que corresponda a Encoding.GetString.

En el ejemplo siguiente se muestra la diferencia entre usar los Encoding.GetString métodos y Decoder.GetChars para descodificar una matriz de bytes Unicode. En el ejemplo se codifica una cadena que contiene algunos caracteres Unicode en un archivo y, a continuación, se usan los dos métodos de descodificación para descodificarlos diez bytes a la vez. Dado que un par suplente se produce en los bytes décimo y undécimo, se descodifica en llamadas de método independientes. Como se muestra en la salida, el Encoding.GetString método no puede descodificar correctamente los bytes y, en su lugar, los reemplaza por U+FFFD (REPLACEMENT CHARACTER). Por otro lado, el Decoder.GetChars método puede descodificar correctamente la matriz de bytes para obtener la cadena original.

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

Elección de una estrategia de reserva

Cuando un método intenta codificar o descodificar un carácter, pero no existe ninguna asignación, debe implementar una estrategia de contingencia que determine cómo se debe gestionar la falta de asignación. Hay tres tipos de estrategias de reserva:

  • Alternativa más adecuada

  • Alternativa de reemplazo

  • Reserva de excepciones

Importante

Los problemas más comunes en las operaciones de codificación se producen cuando un carácter Unicode no se puede asignar a una codificación de página de códigos determinada. Los problemas más comunes en las operaciones de descodificación se producen cuando las secuencias de bytes no válidas no se pueden traducir en caracteres Unicode válidos. Por estas razones, debe saber qué estrategia de reserva usa un objeto de codificación determinado. Siempre que sea posible, debe especificar la estrategia de reserva utilizada por un objeto de codificación al crear una instancia del objeto.

Alternativa de mejor ajuste

Cuando un carácter no tiene una coincidencia exacta en la codificación de destino, el codificador puede intentar asignarlo a un carácter similar. (El retroceso más adecuado es principalmente un problema de codificación en lugar de un problema de descodificación. Hay muy pocas páginas de códigos que contienen caracteres que no se pueden asignar correctamente a Unicode). El retroceso más adecuado es el valor predeterminado para las codificaciones de página de códigos y de juego de caracteres de doble byte recuperadas por las sobrecargas de Encoding.GetEncoding(Int32) y Encoding.GetEncoding(String).

Nota:

En teoría, las clases de codificación Unicode proporcionadas en .NET (UTF8Encoding, UnicodeEncoding, y UTF32Encoding) admiten todos los caracteres de cada conjunto de caracteres, por lo que se pueden utilizar para eliminar los problemas de retroceso de mejor coincidencia.

Las estrategias más adecuadas varían para diferentes páginas de códigos. Por ejemplo, para algunas páginas de códigos, los caracteres latinos de ancho completo se asignan a los caracteres latinos de ancho medio más comunes. En el caso de otras páginas de código, esta asignación no se realiza. Incluso bajo una estrategia de mejor ajuste agresivo, no hay ningún ajuste imaginable para algunos caracteres en algunas codificaciones. Por ejemplo, un ideógrafo chino no tiene ninguna asignación razonable a la página de códigos 1252. En este caso, se usa una cadena de reemplazo. De forma predeterminada, esta cadena es solo un SIGNO DE INTERROGACIÓN (U+003F).

Nota:

Las estrategias más adecuadas no se documentan con detalle. Sin embargo, varias páginas de códigos se documentan en el sitio web del Consorcio Unicode . Revise el archivo readme.txt de esa carpeta para obtener una descripción de cómo interpretar los archivos de asignación.

En el ejemplo siguiente se usa la página de códigos 1252 (la página de códigos de Windows para lenguas de Europa occidental) para ilustrar el mapeo óptimo y sus desventajas. El Encoding.GetEncoding(Int32) método se usa para recuperar un objeto de codificación para la página de códigos 1252. De forma predeterminada, usa una asignación óptima para caracteres Unicode que no admite. En el ejemplo se crea una instancia de una cadena que contiene tres caracteres no ASCII : CIRCLED LATIN CAPITAL LETTER S (U+24C8), SUPERSCRIPT FIVE (U+2075) y INFINITY (U+221E): separados por espacios. Como se muestra en la salida del ejemplo, cuando la cadena está codificada, los tres caracteres originales que no son de espacio se reemplazan por QUESTION MARK (U+003F), DIGIT FIVE (U+0035) y DIGIT EIGHT (U+0038). DIGIT EIGHT es un reemplazo particularmente deficiente para el carácter INFINITY no admitido y QUESTION MARK indica que no hay ninguna asignación disponible para el carácter original.

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

El mapeo más adecuado es el comportamiento predeterminado de un objeto Encoding que codifica datos Unicode en datos de página de códigos. Existen aplicaciones heredadas que dependen de este comportamiento. Sin embargo, la mayoría de las aplicaciones nuevas deben evitar la conducta de ajuste óptimo por motivos de seguridad. Por ejemplo, las aplicaciones no deben someter un nombre de dominio a una codificación de mejor ajuste.

Nota:

También puede implementar una asignación de reserva más adecuada para una codificación. Para obtener más información, consulte la sección Implementación de una estrategia de reserva personalizada.

Si el método de reserva predilecto es el predeterminado para un objeto de codificación, puede seleccionar otra estrategia de sustitución al recuperar un objeto Encoding llamando a la sobrecarga Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback) o Encoding.GetEncoding(String, EncoderFallback, DecoderFallback). En la sección siguiente se incluye un ejemplo que reemplaza cada carácter que no se puede asignar a la página de códigos 1252 con un asterisco (*).

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

Alternativa de reemplazo

Cuando un carácter no tiene una coincidencia exacta en el esquema de destino, pero no hay ningún carácter adecuado al que se pueda asignar, la aplicación puede especificar un carácter de reemplazo o una cadena. Este es el comportamiento predeterminado del descodificador Unicode, que reemplaza cualquier secuencia de dos bytes que no pueda descodificar con REPLACEMENT_CHARACTER (U+FFFD). También es el comportamiento predeterminado de la ASCIIEncoding clase , que reemplaza cada carácter que no puede codificar ni descodificar por un signo de interrogación. En el ejemplo siguiente se muestra el reemplazo de caracteres para la cadena Unicode del ejemplo anterior. Como se muestra en la salida, cada carácter que no se puede descodificar en un valor de byte ASCII se reemplaza por 0x3F, que es el código ASCII de un signo de interrogación.

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 incluye las clases EncoderReplacementFallback y DecoderReplacementFallback, que sustituyen una cadena de reemplazo si un carácter no se asigna de forma exacta en una operación de codificación o descodificación. Por defecto, esta cadena de reemplazo es un signo de interrogación, pero puede llamar a una sobrecarga del constructor de clase para elegir una cadena diferente. Normalmente, la cadena de reemplazo es un carácter único, aunque esto no es obligatorio. En el ejemplo siguiente se cambia el comportamiento del codificador de página de códigos 1252 mediante la instanciación de un objeto EncoderReplacementFallback que usa un asterisco (*) como una cadena de reemplazo.

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

Nota:

También puede implementar una clase de reemplazo para una codificación. Para obtener más información, consulte la sección Implementación de una estrategia de reserva personalizada.

Además de QUESTION MARK (U+003F), el CARÁCTER DE REEMPLAZO Unicode (U+FFFD) se usa normalmente como una cadena de reemplazo, especialmente al descodificar secuencias de bytes que no se pueden traducir correctamente a caracteres Unicode. Sin embargo, puede elegir cualquier cadena de reemplazo y puede contener varios caracteres.

Reserva de excepciones

En lugar de proporcionar un mejor ajuste o una cadena de reemplazo, un codificador puede lanzar un EncoderFallbackException si no puede codificar un conjunto de caracteres, y un decodificador puede lanzar un DecoderFallbackException si no puede decodificar una matriz de bytes. Para generar una excepción en las operaciones de codificación y descodificación, debes proporcionar un objeto EncoderExceptionFallback y un objeto DecoderExceptionFallback respectivamente a la función Encoding.GetEncoding(String, EncoderFallback, DecoderFallback). En el ejemplo siguiente se muestra el mecanismo de respaldo de excepción con la clase 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.

Nota:

También puede implementar un controlador de excepciones personalizado para una operación de codificación. Para obtener más información, consulte la sección Implementación de una estrategia de reserva personalizada.

Los EncoderFallbackException objetos y DecoderFallbackException proporcionan la siguiente información sobre la condición que provocó la excepción:

Aunque los EncoderFallbackException objetos y DecoderFallbackException proporcionan información de diagnóstico adecuada sobre la excepción, no proporcionan acceso al búfer de codificación o descodificación. Por lo tanto, no permiten reemplazar ni corregir datos no válidos dentro del método de codificación o descodificación.

Implementación de una estrategia de contingencia personalizada

Además del mapeo más adecuado implementado internamente por las páginas de código, .NET incluye las siguientes clases para implementar una estrategia de falla:

Además, puede implementar una solución personalizada que use la alternativa de mejor ajuste, la alternativa de reemplazo o la alternativa de excepción con estos pasos:

  1. Derive una clase de EncoderFallback para las operaciones de codificación y de DecoderFallback para las operaciones de descodificación.

  2. Derive una clase de EncoderFallbackBuffer para las operaciones de codificación y de DecoderFallbackBuffer para las operaciones de descodificación.

  3. Para el manejo de excepciones, si las clases predefinidas EncoderFallbackException y DecoderFallbackException no satisfacen sus necesidades, derive una clase de un objeto de excepción como Exception o ArgumentException.

Derivación de EncoderFallback o DecoderFallback

Para implementar una solución personalizada de reserva, debe crear una clase que herede de EncoderFallback para las operaciones de codificación y de DecoderFallback para las operaciones de decodificación. Las instancias de estas clases se pasan al método Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) y sirven como intermediarios entre la clase de codificación y la implementación de retorno.

Al crear una solución de reserva personalizada para un codificador o descodificador, debe implementar los siguientes miembros:

Derivación de EncoderFallbackBuffer o DecoderFallbackBuffer

Para implementar una solución de reserva personalizada, también debe crear una clase que herede de EncoderFallbackBuffer para las operaciones de codificación y de DecoderFallbackBuffer para las operaciones de descodificación. Las instancias de estas clases son devueltas por el método CreateFallbackBuffer de las clases EncoderFallback y DecoderFallback. El EncoderFallback.CreateFallbackBuffer codificador llama al método cuando encuentra el primer carácter que no puede codificar y el DecoderFallback.CreateFallbackBuffer descodificador llama al método cuando encuentra uno o más bytes que no puede descodificar. Las clases EncoderFallbackBuffer y DecoderFallbackBuffer proporcionan la implementación de reserva. Cada instancia representa un búfer que contiene los caracteres de reserva que reemplazarán el carácter que no se puede codificar o la secuencia de bytes que no se puede descodificar.

Al crear una solución de reserva personalizada para un codificador o descodificador, debe implementar los siguientes miembros:

Si la implementación de reserva es una reserva de mejor ajuste o una reserva de reemplazo, las clases derivadas de EncoderFallbackBuffer y DecoderFallbackBuffer también mantienen dos campos de instancia privada: el número exacto de caracteres en el búfer y el índice del siguiente carácter del búfer que se va a devolver.

Ejemplo de EncoderFallback

En un ejemplo anterior se usó un reemplazo alternativo para reemplazar los caracteres Unicode que no se corresponden con caracteres ASCII por un asterisco (*). En el ejemplo siguiente se usa una implementación de reserva más adecuada personalizada en su lugar para proporcionar una mejor asignación de caracteres no ASCII.

El código siguiente define una clase denominada CustomMapper que se deriva de EncoderFallback para controlar la asignación de mejor ajuste de caracteres no ASCII. Su CreateFallbackBuffer método devuelve un CustomMapperFallbackBuffer objeto , que proporciona la EncoderFallbackBuffer implementación. La CustomMapper clase utiliza un Dictionary<TKey,TValue> objeto para almacenar las asignaciones de caracteres Unicode no admitidos (la clave) y sus caracteres de 8 bits correspondientes (que se almacenan en dos bytes consecutivos dentro de un entero de 64 bits). Para que esta asignación esté disponible para el búfer de reserva, la instancia CustomMapper se pasa como parámetro al constructor de la clase CustomMapperFallbackBuffer. Dado que la asignación más larga es la cadena "INF" para el carácter Unicode U+221E, la MaxCharCount propiedad devuelve 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

El código siguiente define la CustomMapperFallbackBuffer clase , que se deriva de EncoderFallbackBuffer. El diccionario que contiene asignaciones más adecuadas y que se define en la CustomMapper instancia está disponible en su constructor de clases. Su Fallback método devuelve true si alguno de los caracteres Unicode que el codificador ASCII no puede codificar se define en el diccionario de asignación; de lo contrario, devuelve false. Para cada retroceso, la variable privada count indica el número de caracteres que quedan por devolver, y la variable privada index indica la posición en el búfer de cadenas, charsToReturn, del siguiente carácter que queda por devolver.

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

A continuación, el código siguiente crea una instancia del CustomMapper objeto y pasa una instancia de él al Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) método . La salida indica que la implementación de sustitución más adecuada gestiona correctamente los tres caracteres no ASCII de la cadena original.

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

Consulte también