Jak używać klas kodowania znaków na platformie .NET

W tym artykule wyjaśniono, jak używać klas zapewnianych przez platformę .NET do kodowania i dekodowania tekstu przy użyciu różnych schematów kodowania. W instrukcjach założono, że przeczytasz wprowadzenie do kodowania znaków na platformie .NET.

Kodery i dekodery

Platforma .NET udostępnia klasy kodowania, które koduje i dekoduje tekst przy użyciu różnych systemów kodowania. Na przykład UTF8Encoding klasa opisuje reguły kodowania do i dekodowania z utF-8. Platforma .NET używa kodowania UTF-16 (reprezentowanego przez klasę UnicodeEncoding ) dla string wystąpień. Kodery i dekodery są dostępne dla innych schematów kodowania.

Kodowanie i dekodowanie może również obejmować walidację. Na przykład klasa sprawdza wszystkie char wystąpienia w zakresie zastępczym, UnicodeEncoding aby upewnić się, że są w prawidłowych parach zastępczych. Strategia rezerwowa określa sposób obsługi nieprawidłowych znaków przez koder lub sposób obsługi nieprawidłowych bajtów przez dekoder.

Ostrzeżenie

Klasy kodowania platformy .NET umożliwiają przechowywanie i konwertowanie danych znaków. Nie powinny być używane do przechowywania danych binarnych w postaci ciągu. W zależności od używanego kodowania konwertowanie danych binarnych na format ciągu za pomocą klas kodowania może spowodować nieoczekiwane zachowanie i wygenerować niedokładne lub uszkodzone dane. Aby przekonwertować dane binarne na formularz ciągu, użyj Convert.ToBase64String metody .

Wszystkie klasy kodowania znaków na platformie .NET dziedziczą z System.Text.Encoding klasy , która jest abstrakcyjną klasą, która definiuje funkcjonalność wspólną dla wszystkich kodowań znaków. Aby uzyskać dostęp do poszczególnych obiektów kodowania zaimplementowanych na platformie .NET, wykonaj następujące czynności:

  • Użyj właściwości statycznych Encoding klasy, które zwracają obiekty reprezentujące standardowe kodowania znaków dostępne na platformie .NET (ASCII, UTF-7, UTF-8, UTF-16 i UTF-32). Na przykład Encoding.Unicode właściwość zwraca UnicodeEncoding obiekt. Każdy obiekt używa rezerwowego zastąpienia do obsługi ciągów, których nie może kodować i bajtów, których nie może dekodować. Aby uzyskać więcej informacji, zobacz Wymiana rezerwowa.

  • Wywołaj konstruktor klasy kodowania. Obiekty dla kodowań ASCII, UTF-7, UTF-8, UTF-16 i UTF-32 można utworzyć w ten sposób. Domyślnie każdy obiekt używa rezerwowego zastępczego do obsługi ciągów, których nie można zakodować i bajtów, których nie można dekodować, ale można określić, że zamiast tego należy zgłosić wyjątek. Aby uzyskać więcej informacji, zobacz Rezerwa zastępcza i Rezerwa wyjątku.

  • Wywołaj Encoding(Int32) konstruktor i przekaż go jako liczbę całkowitą reprezentującą kodowanie. Standardowe obiekty kodowania używają rezerwowych zamian, a strony kodowej i obiektów kodowania dwubajtowych (DBCS) używają najlepiej dopasowanych rezerwowych do obsługi ciągów, których nie mogą kodować i bajtów, których nie mogą dekodować. Aby uzyskać więcej informacji, zobacz Najlepsze dopasowanie rezerwowe.

  • Wywołaj metodę Encoding.GetEncoding , która zwraca wszystkie standardowe, kodowe strony lub kodowanie DBCS dostępne na platformie .NET. Przeciążenia umożliwiają określenie obiektu rezerwowego zarówno dla kodera, jak i dekodera.

Możesz pobrać informacje o wszystkich kodowaniach dostępnych na platformie .NET, wywołując metodę Encoding.GetEncodings . Platforma .NET obsługuje schematy kodowania znaków wymienione w poniższej tabeli.

Klasa kodowania opis
ASCII Koduje ograniczony zakres znaków przy użyciu niższych siedmiu bitów bajtu. Ponieważ to kodowanie obsługuje tylko wartości znaków z U+0000 przez U+007F, w większości przypadków jest nieodpowiednie dla aplikacji międzynarodowych.
UTF-7 Reprezentuje znaki jako sekwencje 7-bitowych znaków ASCII. Znaki Unicode inne niż ASCII są reprezentowane przez sekwencję ucieczki znaków ASCII. Protokół UTF-7 obsługuje protokoły, takie jak poczta e-mail i grupa dyskusyjna. Jednak utF-7 nie jest szczególnie bezpieczny ani niezawodny. W niektórych przypadkach zmiana jednego bitu może radykalnie zmienić interpretację całego ciągu UTF-7. W innych przypadkach różne ciągi UTF-7 mogą kodować ten sam tekst. W przypadku sekwencji zawierających znaki inne niż ASCII kodowanie UTF-7 wymaga więcej miejsca niż UTF-8, a kodowanie/dekodowanie jest wolniejsze. W związku z tym należy użyć utF-8 zamiast UTF-7, jeśli to możliwe.
UTF-8 Reprezentuje każdy punkt kodu Unicode jako sekwencję od jednej do czterech bajtów. Protokół UTF-8 obsługuje 8-bitowe rozmiary danych i dobrze współpracuje z wieloma istniejącymi systemami operacyjnymi. W przypadku zakresu znaków ASCII kodowanie UTF-8 jest identyczne z kodowaniem ASCII i umożliwia szerszy zestaw znaków. Jednak w przypadku skryptów języka chińskiego i koreańskiego (CJK) kod UTF-8 może wymagać trzech bajtów dla każdego znaku i może powodować większe rozmiary danych niż UTF-16. Czasami ilość danych ASCII, takich jak tagi HTML, uzasadnia zwiększenie rozmiaru zakresu CJK.
UTF-16 Reprezentuje każdy punkt kodu Unicode jako sekwencję co najmniej jednej 16-bitowej liczby całkowitej. Większość typowych znaków Unicode wymaga tylko jednego punktu kodu UTF-16, chociaż znaki dodatkowe Unicode (U+10000 i nowsze) wymagają dwóch punktów kodu zastępczego UTF-16. Obsługiwane są zarówno zamówienia bajtów little-endian, jak i big-endian. Kodowanie UTF-16 jest używane przez środowisko uruchomieniowe języka wspólnego do reprezentowania Char i String wartości i jest używane przez system operacyjny Windows do reprezentowania WCHAR wartości.
UTF-32 Reprezentuje każdy punkt kodu Unicode jako 32-bitową liczbę całkowitą. Obsługiwane są zarówno zamówienia bajtów little-endian, jak i big-endian. Kodowanie UTF-32 jest używane, gdy aplikacje chcą uniknąć zachowania zastępczego punktu kodu kodowania UTF-16 w systemach operacyjnych, dla których zakodowane miejsce jest zbyt ważne. Pojedyncze glify renderowane na wyświetlaczu mogą być nadal kodowane z więcej niż jednym znakiem UTF-32.
Kodowanie ANSI/ISO Zapewnia obsługę różnych stron kodu. W systemach operacyjnych Windows strony kodu są używane do obsługi określonego języka lub grupy języków. Aby uzyskać tabelę zawierającą listę stron kodu obsługiwanych przez platformę .NET, zobacz klasę Encoding . Obiekt kodowania dla określonej strony kodu można pobrać, wywołując metodę Encoding.GetEncoding(Int32) . Strona kodowa zawiera 256 punktów kodu i jest oparta na zera. W większości stron kodu punkty kodu od 0 do 127 reprezentują zestaw znaków ASCII, a punkty kodu od 128 do 255 różnią się znacznie między stronami kodu. Na przykład strona kodowa 1252 zawiera znaki dla systemów pisania łacińskiego, w tym angielskiego, niemieckiego i francuskiego. Ostatnie 128 punktów kodu na stronie kodowej 1252 zawiera znaki wyróżniające. Strona kodowa 1253 zawiera kody znaków wymagane w greckim systemie pisania. Ostatnie 128 punktów kodu na stronie kodowej 1253 zawiera znaki greckie. W związku z tym aplikacja, która opiera się na stronach kodowych ANSI, nie może przechowywać języka greckiego i niemieckiego w tym samym strumieniu tekstowym, chyba że zawiera identyfikator wskazujący przywołyną stronę kodową.
Kodowania zestawu znaków dwubajtowych (DBCS) Obsługuje języki, takie jak chiński, japoński i koreański, zawierające więcej niż 256 znaków. W dbCS para punktów kodu (dwubajtowy) reprezentuje każdy znak. Właściwość Encoding.IsSingleByte zwraca kodowanie false DBCS. Obiekt kodowania dla określonej usługi DBCS można pobrać, wywołując metodę Encoding.GetEncoding(Int32) . Gdy aplikacja obsługuje dane DBCS, pierwszy bajt znaku DBCS (bajt ołowiu) jest przetwarzany w połączeniu z bajtem końcowym, który natychmiast następuje po nim. Ponieważ jedna para dwubajtowych punktów kodu może reprezentować różne znaki w zależności od strony kodowej, ten schemat nadal nie zezwala na kombinację dwóch języków, takich jak japoński i chiński, w tym samym strumieniu danych.

Te kodowania umożliwiają pracę z znakami Unicode oraz kodowaniem, które są najczęściej używane w starszych aplikacjach. Ponadto można utworzyć kodowanie niestandardowe, definiując klasę, która pochodzi z Encoding elementów członkowskich i zastępuje jej składowe.

Obsługa kodowania platformy .NET Core

Domyślnie platforma .NET Core nie udostępnia żadnych kodowań stron kodowych innych niż strona kodowa 28591 i kodowanie Unicode, takie jak UTF-8 i UTF-16. Można jednak dodać kodowanie stron kodowych znajdujących się w standardowych aplikacjach systemu Windows przeznaczonych dla platformy .NET do aplikacji. Aby uzyskać więcej informacji, zobacz CodePagesEncodingProvider temat.

Wybieranie klasy kodowania

Jeśli masz możliwość wybrania kodowania, które ma być używane przez aplikację, należy użyć kodowania Unicode, najlepiej albo UTF8Encoding lub UnicodeEncoding. (.NET obsługuje również trzecie kodowanie Unicode, UTF32Encoding.)

Jeśli planujesz użyć kodowania ASCII (ASCIIEncoding), wybierz UTF8Encoding zamiast tego. Dwa kodowania są identyczne dla zestawu znaków ASCII, ale UTF8Encoding mają następujące zalety:

  • Może reprezentować każdy znak Unicode, natomiast ASCIIEncoding obsługuje tylko wartości znaków Unicode z zakresu od U+0000 do U+007F.

  • Zapewnia wykrywanie błędów i lepsze zabezpieczenia.

  • Został dostrojony tak szybko, jak to możliwe i powinien być szybszy niż jakiekolwiek inne kodowanie. Nawet w przypadku zawartości, która jest całkowicie ASCII, operacje wykonywane za pomocą UTF8Encoding polecenia są szybsze niż operacje wykonywane za pomocą ASCIIEncodingpolecenia .

Należy rozważyć użycie ASCIIEncoding tylko dla starszych aplikacji. Jednak nawet w przypadku starszych aplikacji UTF8Encoding może być lepszym wyborem z następujących powodów (przy założeniu ustawień domyślnych):

  • Jeśli aplikacja ma zawartość, która nie jest ściśle ASCII i koduje ją przy ASCIIEncodingużyciu , każdy znak inny niż ASCII koduje jako znak zapytania (?). Jeśli aplikacja zdekoduje te dane, informacje zostaną utracone.

  • Jeśli aplikacja ma zawartość, która nie jest ściśle ASCII i koduje ją za pomocą UTF8Encodingpolecenia , wynik wydaje się niezrozumiały, jeśli jest interpretowany jako ASCII. Jeśli jednak aplikacja używa dekodera UTF-8 do dekodowania tych danych, dane są wykonywane pomyślnie.

W aplikacji internetowej znaki wysyłane do klienta w odpowiedzi na żądanie internetowe powinny odzwierciedlać kodowanie używane na kliencie. W większości przypadków należy ustawić HttpResponse.ContentEncoding właściwość na wartość zwracaną przez właściwość, aby wyświetlić tekst w kodowaniu oczekiwanym przez HttpRequest.ContentEncoding użytkownika.

Używanie obiektu kodowania

Koder konwertuje ciąg znaków (najczęściej znaków Unicode) na odpowiednik liczbowy (bajt). Na przykład można użyć kodera ASCII, aby przekonwertować znaki Unicode na ASCII, aby można było je wyświetlić w konsoli programu . Aby wykonać konwersję, należy wywołać metodę Encoding.GetBytes . Jeśli chcesz określić, ile bajtów jest potrzebnych do przechowywania zakodowanych znaków przed wykonaniem kodowania, możesz wywołać metodę GetByteCount .

W poniższym przykładzie użyto pojedynczej tablicy bajtów do kodowania ciągów w dwóch oddzielnych operacjach. Utrzymuje indeks, który wskazuje pozycję początkową w tablicy bajtów dla następnego zestawu bajtów zakodowanych w formacie ASCII. Wywołuje metodę ASCIIEncoding.GetByteCount(String) , aby upewnić się, że tablica bajtów jest wystarczająco duża, aby pomieścić zakodowany ciąg. Następnie wywołuje metodę ASCIIEncoding.GetBytes(String, Int32, Int32, Byte[], Int32) w celu zakodowania znaków w ciągu.

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.
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.

Dekoder konwertuje tablicę bajtów, która odzwierciedla kodowanie określonego znaku w zestawie znaków w tablicy znaków lub w ciągu. Aby zdekodować tablicę bajtów do tablicy znaków, należy wywołać metodę Encoding.GetChars . Aby zdekodować tablicę bajtów do ciągu, należy wywołać metodę GetString . Jeśli chcesz określić, ile znaków jest potrzebnych do przechowywania zdekodowanych bajtów przed wykonaniem dekodowania, możesz wywołać metodę GetCharCount .

Poniższy przykład koduje trzy ciągi, a następnie dekoduje je w jedną tablicę znaków. Utrzymuje indeks, który wskazuje pozycję początkową w tablicy znaków dla następnego zestawu zdekodowanych znaków. Wywołuje metodę GetCharCount , aby upewnić się, że tablica znaków jest wystarczająco duża, aby pomieścić wszystkie zdekodowane znaki. Następnie wywołuje metodę ASCIIEncoding.GetChars(Byte[], Int32, Int32, Char[], Int32) w celu dekodowania tablicy bajtów.

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.
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.

Metody kodowania i dekodowania klasy pochodzącej z Encoding klasy zostały zaprojektowane tak, aby działały na pełnym zestawie danych. Oznacza to, że wszystkie dane, które mają być zakodowane lub dekodowane, są dostarczane w jednym wywołaniu metody. Jednak w niektórych przypadkach dane są dostępne w strumieniu, a dane do zakodowania lub dekodowania mogą być dostępne tylko z oddzielnych operacji odczytu. Wymaga to operacji kodowania lub dekodowania, aby zapamiętać dowolny zapisany stan z poprzedniego wywołania. Metody klas pochodnych i EncoderDecoder są w stanie obsługiwać operacje kodowania i dekodowania obejmujące wiele wywołań metod.

Obiekt Encoder dla określonego kodowania jest dostępny z właściwości tego kodowania Encoding.GetEncoder . Decoder Obiekt dla określonego kodowania jest dostępny z właściwości tego kodowaniaEncoding.GetDecoder. W przypadku operacji dekodowania należy pamiętać, że klasy pochodzące z Decoder metody obejmują metodę Decoder.GetChars , ale nie mają metody odpowiadającej metodzie Encoding.GetString.

Poniższy przykład ilustruje różnicę między używaniem Encoding.GetString metod i Decoder.GetChars do dekodowania tablicy bajtów Unicode. W przykładzie koduje ciąg zawierający kilka znaków Unicode do pliku, a następnie używa dwóch metod dekodowania do dekodowania ich dziesięć bajtów naraz. Ponieważ para zastępcza występuje w dziesiątych i jedenastych bajtach, jest dekodowana w osobnych wywołaniach metod. Jak pokazują dane wyjściowe, Encoding.GetString metoda nie może poprawnie dekodować bajtów i zamiast tego zastępuje je znakiem U+FFFD (ZNAK ZASTĘPCZY). Z drugiej strony Decoder.GetChars metoda umożliwia pomyślne dekodowanie tablicy bajtów w celu pobrania oryginalnego ciągu.

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
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

Wybieranie strategii powrotu

Gdy metoda próbuje kodować lub dekodować znak, ale nie istnieje mapowanie, musi zaimplementować strategię rezerwową określającą sposób obsługi mapowania niepowodzeń. Istnieją trzy typy strategii rezerwowych:

  • Powrót najlepiej dopasowany

  • Rezerwowa zamiana

  • Powrót wyjątku

Ważne

Najczęstsze problemy podczas operacji kodowania występują, gdy nie można zamapować znaku Unicode na określone kodowanie strony kodowej. Najczęstsze problemy podczas dekodowania operacji występują, gdy nie można przetłumaczyć nieprawidłowych sekwencji bajtów na prawidłowe znaki Unicode. Z tych powodów należy wiedzieć, która strategia rezerwowa używa określonego obiektu kodowania. Jeśli to możliwe, należy określić strategię rezerwową używaną przez obiekt kodowania podczas tworzenia wystąpienia obiektu.

Powrót do najlepszego dopasowania

Gdy znak nie ma dokładnego dopasowania w kodowaniu docelowym, koder może spróbować zmapować go na podobny znak. (Najlepiej dopasować rezerwowy jest głównie kodowanie, a nie dekodowanie problemu. Istnieje bardzo mało stron kodu zawierających znaki, których nie można pomyślnie zamapować na Unicode). Opcja rezerwowa najlepszego dopasowania jest domyślna dla kodów strony kodu i kodowań zestawów znaków dwubajtowych, które są pobierane przez Encoding.GetEncoding(Int32) przeciążenia i Encoding.GetEncoding(String) .

Uwaga

Teoretycznie klasy kodowania Unicode dostępne na platformie .NET (UTF8Encoding, UnicodeEncodingi UTF32Encoding) obsługują każdy znak w każdym zestawie znaków, dzięki czemu mogą służyć do eliminowania problemów z rezerwowym dopasowaniem.

Strategie najlepszego dopasowania różnią się w zależności od różnych stron kodu. Na przykład w przypadku niektórych stron kodu znaki łacińskie o pełnej szerokości są mapowane na bardziej typowe znaki łacińskie o połowie szerokości. W przypadku innych stron kodu to mapowanie nie jest wykonywane. Nawet w ramach agresywnej strategii najlepiej dopasowanej, nie można sobie wyobrazić niektórych znaków w niektórych kodowaniu. Na przykład chiński ideograf nie ma rozsądnego mapowania na stronę kodową 1252. W tym przypadku jest używany ciąg zastępczy. Domyślnie ten ciąg jest tylko jednym znakiem ZAPYTANIA (U+003F).

Uwaga

Strategie najlepszego dopasowania nie są szczegółowo udokumentowane. Jednak kilka stron kodu jest udokumentowanych na stronie internetowej Unicode Consortium . Przejrzyj plik readme.txt w tym folderze, aby uzyskać opis sposobu interpretowania plików mapowania.

W poniższym przykładzie użyto strony kodowej 1252 (strony kodowej systemu Windows dla języków zachodnioeuropejskich), aby zilustrować mapowanie najlepiej dopasowane i jego wady. Metoda Encoding.GetEncoding(Int32) służy do pobierania obiektu kodowania dla strony kodowej 1252. Domyślnie używa mapowania najlepiej dopasowanego do znaków Unicode, które nie są obsługiwane. Przykład tworzy wystąpienie ciągu zawierającego trzy znaki inne niż ASCII — CIRCLED LATIN CAPITAL LETTER S (U+24C8), SUPERSCRIPT FIVE (U+2075) i INFINITY (U+221E) — oddzielone spacjami. Jak pokazuje dane wyjściowe z przykładu, gdy ciąg jest zakodowany, trzy oryginalne znaki inne niż spacje są zastępowane znakiem ZAPYTANIA (U+003F), CYFRĄ PIĘĆ (U+0035) i CYFRA OSIEM (U+0038). CYFRA OSIEM to szczególnie słaba zamiana nieobsługiwanego znaku NIESKOŃCZONOŚCI, a znak ZAPYTANIA wskazuje, że nie ma dostępnego mapowania dla oryginalnego znaku.

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
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

Mapowanie najlepszego dopasowania to domyślne zachowanie Encoding obiektu, który koduje dane Unicode do danych strony kodu, a istnieją starsze aplikacje, które opierają się na tym zachowaniu. Jednak większość nowych aplikacji powinna unikać najlepszego zachowania ze względów bezpieczeństwa. Na przykład aplikacje nie powinny umieszczać nazwy domeny za pomocą kodowania najlepiej dopasowanego.

Uwaga

Możesz również zaimplementować niestandardowe mapowanie rezerwowe najlepiej dopasowane do kodowania. Aby uzyskać więcej informacji, zobacz sekcję Implementowanie niestandardowej strategii rezerwowej.

Jeśli najlepszym dopasowaniem rezerwowym jest domyślny obiekt kodowania, możesz wybrać inną strategię rezerwową podczas pobierania Encoding obiektu przez wywołanie Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback) metody lub Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) . Poniższa sekcja zawiera przykład, który zastępuje każdy znak, którego nie można zamapować na stronę kodową 1252 gwiazdką (*).

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
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

Rezerwowy zamiana

Jeśli znak nie ma dokładnego dopasowania w schemacie docelowym, ale nie ma odpowiedniego znaku, do którego można go zamapować, aplikacja może określić znak zastępczy lub ciąg. Jest to domyślne zachowanie dekodera Unicode, który zastępuje dowolną sekwencję dwu bajtów, której nie można dekodować za pomocą REPLACEMENT_CHARACTER (U+FFFD). Jest to również domyślne zachowanie ASCIIEncoding klasy, która zastępuje każdy znak, którego nie może kodować ani dekodować znakiem zapytania. Poniższy przykład ilustruje zamianę znaków dla ciągu Unicode z poprzedniego przykładu. Jak pokazują dane wyjściowe, każdy znak, którego nie można zdekodować do wartości bajtu ASCII, jest zastępowany przez 0x3F, czyli kod ASCII dla znaku zapytania.

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
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

Platforma .NET zawiera EncoderReplacementFallback klasy i DecoderReplacementFallback , które zastępują ciąg zastępczy, jeśli znak nie mapuje dokładnie w operacji kodowania lub dekodowania. Domyślnie ten ciąg zastępczy jest znakiem zapytania, ale można wywołać przeciążenie konstruktora klasy, aby wybrać inny ciąg. Zazwyczaj ciąg zastępczy jest pojedynczym znakiem, chociaż nie jest to wymagane. Poniższy przykład zmienia zachowanie kodera strony kodowej 1252 przez utworzenie wystąpienia EncoderReplacementFallback obiektu, który używa gwiazdki (*) jako ciągu zastępczego.

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
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

Uwaga

Można również zaimplementować klasę zastępczą dla kodowania. Aby uzyskać więcej informacji, zobacz sekcję Implementowanie niestandardowej strategii rezerwowej.

Oprócz znaku ZAPYTANIA (U+003F) znak ZASTĘPCZY Unicode (U+FFFD) jest często używany jako ciąg zastępczy, szczególnie w przypadku dekodowania sekwencji bajtów, których nie można pomyślnie przetłumaczyć na znaki Unicode. Możesz jednak wybrać dowolny ciąg zastępczy i może zawierać wiele znaków.

Rezerwa wyjątku

Zamiast dostarczać najlepiej dopasowany rezerwowy lub ciąg zastępczy, koder może zgłosić EncoderFallbackException , jeśli nie może zakodować zestawu znaków, a dekoder może zgłosić DecoderFallbackException , jeśli nie może dekodować tablicy bajtów. Aby zgłosić wyjątek w operacjach kodowania i dekodowania, należy odpowiednio podać EncoderExceptionFallback obiekt i DecoderExceptionFallback obiekt do Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) metody . W poniższym przykładzie pokazano rezerwowanie wyjątków z klasą 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{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.
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.

Uwaga

Można również zaimplementować niestandardową procedurę obsługi wyjątków dla operacji kodowania. Aby uzyskać więcej informacji, zobacz sekcję Implementowanie niestandardowej strategii rezerwowej.

Obiekty EncoderFallbackException i DecoderFallbackException zawierają następujące informacje o warunku, który spowodował wyjątek:

EncoderFallbackException Chociaż obiekty i DecoderFallbackException zapewniają odpowiednie informacje diagnostyczne dotyczące wyjątku, nie zapewniają dostępu do buforu kodowania ani dekodowania. W związku z tym nie zezwalają na zastępowanie lub poprawianie nieprawidłowych danych w metodzie kodowania lub dekodowania.

Implementowanie niestandardowej strategii rezerwowej

Oprócz najlepszego mapowania, które jest implementowane wewnętrznie przez strony kodu, platforma .NET obejmuje następujące klasy do implementowania strategii rezerwowej:

Ponadto można zaimplementować rozwiązanie niestandardowe, które używa najlepiej dopasowanego rezerwowego, rezerwowego zastąpienia lub rezerwowego wyjątku, wykonując następujące kroki:

  1. Utwórz klasę na EncoderFallback podstawie operacji kodowania i z DecoderFallback operacji dekodowania.

  2. Utwórz klasę na EncoderFallbackBuffer podstawie operacji kodowania i z DecoderFallbackBuffer operacji dekodowania.

  3. W przypadku rezerwowego wyjątku, jeśli wstępnie zdefiniowane EncoderFallbackException klasy i DecoderFallbackException nie spełniają Twoich potrzeb, należy utworzyć klasę z obiektu wyjątku, takiego jak Exception lub ArgumentException.

Wyprowadzanie z koderaFallback lub DecoderFallback

Aby zaimplementować niestandardowe rozwiązanie rezerwowe, należy utworzyć klasę dziedziczącą z EncoderFallback operacji kodowania i z DecoderFallback operacji dekodowania. Wystąpienia tych klas są przekazywane do Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) metody i służą jako pośrednik między klasą kodowania a implementacją rezerwową.

Podczas tworzenia niestandardowego rozwiązania rezerwowego dla kodera lub dekodera należy zaimplementować następujące elementy członkowskie:

Wyprowadzanie z EncoderFallbackBuffer lub DecoderFallbackBuffer

Aby zaimplementować niestandardowe rozwiązanie rezerwowe, należy również utworzyć klasę dziedziczącą z EncoderFallbackBuffer operacji kodowania i z DecoderFallbackBuffer operacji dekodowania. Wystąpienia tych klas są zwracane przez metodę CreateFallbackBufferEncoderFallback i DecoderFallback klasy . EncoderFallback.CreateFallbackBuffer Metoda jest wywoływana przez koder, gdy napotka pierwszy znak, którego nie jest w stanie zakodować, a DecoderFallback.CreateFallbackBuffer metoda jest wywoływana przez dekoder, gdy napotka jeden lub więcej bajtów, że nie jest w stanie dekodować. Klasy EncoderFallbackBuffer i DecoderFallbackBuffer zapewniają implementację rezerwową. Każde wystąpienie reprezentuje bufor zawierający znaki rezerwowe, które zastąpią znak, który nie może być zakodowany lub sekwencja bajtów, której nie można zdekodować.

Podczas tworzenia niestandardowego rozwiązania rezerwowego dla kodera lub dekodera należy zaimplementować następujące elementy członkowskie:

Jeśli implementacja rezerwowa jest najlepszym dopasowaniem rezerwowym lub rezerwą zastępczą, klasy pochodzące z EncoderFallbackBuffer i DecoderFallbackBuffer również utrzymują dwa pola wystąpienia prywatnego: dokładną liczbę znaków w buforze oraz indeks następnego znaku w buforze do zwrócenia.

Przykład koderaFallback

We wcześniejszym przykładzie użyto powrotu zastępczego, aby zastąpić znaki Unicode, które nie odpowiadają znakom ASCII gwiazdką (*). W poniższym przykładzie użyto niestandardowej implementacji rezerwowej najlepiej dopasowanej, aby zapewnić lepsze mapowanie znaków innych niż ASCII.

Poniższy kod definiuje klasę o nazwie CustomMapper , która pochodzi z EncoderFallback , aby obsługiwać najlepsze mapowanie znaków innych niż ASCII. Metoda CreateFallbackBuffer zwraca CustomMapperFallbackBuffer obiekt, który zapewnia implementację EncoderFallbackBuffer . Klasa CustomMapper używa Dictionary<TKey,TValue> obiektu do przechowywania mapowań nieobsługiwanych znaków Unicode (wartość klucza) i odpowiadających im 8-bitowych znaków (które są przechowywane w dwóch kolejnych bajtach w 64-bitowej liczbą całkowitą). Aby to mapowanie było dostępne dla buforu rezerwowego, CustomMapper wystąpienie jest przekazywane jako parametr do konstruktora CustomMapperFallbackBuffer klasy. Ponieważ najdłuższe mapowanie jest ciągiem "INF" dla znaku Unicode U+221E, MaxCharCount właściwość zwraca wartość 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

Poniższy kod definiuje klasę CustomMapperFallbackBuffer , która pochodzi z EncoderFallbackBufferklasy . Słownik zawierający mapowania najlepiej dopasowane i zdefiniowany w wystąpieniu CustomMapper jest dostępny z konstruktora klasy. Metoda Fallback zwraca true , jeśli którykolwiek z znaków Unicode, których koder ASCII nie może kodować, jest zdefiniowany w słowniku mapowania; w przeciwnym razie zwraca wartość false. Dla każdego rezerwowego zmienna prywatna count wskazuje liczbę znaków, które pozostają zwracane, a zmienna prywatna index wskazuje pozycję w buforze ciągów, charsToReturn, następnego znaku, który ma zostać zwrócony.

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

Poniższy kod następnie tworzy wystąpienie CustomMapper obiektu i przekazuje wystąpienie do Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) metody . Dane wyjściowe wskazują, że implementacja rezerwowa najlepiej dopasowana pomyślnie obsługuje trzy znaki inne niż ASCII w oryginalnym ciągu.

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();
      }
   }
}
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

Zobacz też