Partilhar via


Codificação de caracteres no .NET

Este artigo fornece uma introdução aos sistemas de codificação de caracteres usados pelo .NET. O artigo explica como os Stringtipos , Char, Rune, e StringInfo funcionam com Unicode, UTF-16 e UTF-8.

O termo caractere é usado aqui no sentido geral de aquilo que um leitor percebe como um único elemento de visualização. Exemplos comuns são a letra "a", o símbolo "@" e o emoji "🐂". Às vezes, o que parece ser um caractere é, na verdade, composto por vários elementos de exibição independentes, como explica a seção sobre aglomerados de grafemas.

Tipos string e char

Uma instância da string classe representa algum texto. A string é logicamente uma sequência de valores de 16 bits, cada um dos quais é uma instância da char struct. A propriedade string.Length retorna o número de instâncias de char na instância string.

A função de exemplo a seguir imprime os valores em notação hexadecimal de todas as char instâncias em um string:

void PrintChars(string s)
{
    Console.WriteLine($"\"{s}\".Length = {s.Length}");
    for (int i = 0; i < s.Length; i++)
    {
        Console.WriteLine($"s[{i}] = '{s[i]}' ('\\u{(int)s[i]:x4}')");
    }
    Console.WriteLine();
}

Passe o string "Olá" para esta função, e você obtém a seguinte saída:

PrintChars("Hello");
"Hello".Length = 5
s[0] = 'H' ('\u0048')
s[1] = 'e' ('\u0065')
s[2] = 'l' ('\u006c')
s[3] = 'l' ('\u006c')
s[4] = 'o' ('\u006f')

Cada caractere é representado por um único char valor. Esse padrão é válido para a maioria das línguas do mundo. Por exemplo, aqui está a saída para dois caracteres chineses que soam como nǐ hǎo e significam Olá:

PrintChars("你好");
"你好".Length = 2
s[0] = '你' ('\u4f60')
s[1] = '好' ('\u597d')

No entanto, para alguns idiomas e para alguns símbolos e emojis, são necessárias duas char instâncias para representar um único caractere. Por exemplo, compare os caracteres e as instâncias de char na palavra que significa "Osage" na língua Osage:

PrintChars("𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟");
"𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟".Length = 17
s[0] = '�' ('\ud801')
s[1] = '�' ('\udccf')
s[2] = '�' ('\ud801')
s[3] = '�' ('\udcd8')
s[4] = '�' ('\ud801')
s[5] = '�' ('\udcfb')
s[6] = '�' ('\ud801')
s[7] = '�' ('\udcd8')
s[8] = '�' ('\ud801')
s[9] = '�' ('\udcfb')
s[10] = '�' ('\ud801')
s[11] = '�' ('\udcdf')
s[12] = ' ' ('\u0020')
s[13] = '�' ('\ud801')
s[14] = '�' ('\udcbb')
s[15] = '�' ('\ud801')
s[16] = '�' ('\udcdf')

No exemplo anterior, cada caractere, exceto o espaço, é representado por duas char instâncias.

Um único emoji Unicode também é representado por dois chars, como visto no exemplo a seguir mostrando um emoji de boi:

"🐂".Length = 2
s[0] = '�' ('\ud83d')
s[1] = '�' ('\udc02')

Esses exemplos mostram que o valor de string.Length, que indica o número de char instâncias, não indica necessariamente o número de caracteres exibidos. Uma única char instância por si só não representa necessariamente um personagem.

Os char pares que mapeiam para um único caractere são chamados pares substitutos. Para entender como eles funcionam, você precisa entender a codificação Unicode e UTF-16.

Pontos de código Unicode

Unicode é um padrão internacional de codificação para uso em várias plataformas e com várias linguagens e scripts.

O padrão Unicode define mais de 1,1 milhão de pontos de código. Um ponto de código é um valor inteiro que pode variar de 0 a U+10FFFF (decimal 1.114.111). Alguns pontos de código são atribuídos a letras, símbolos ou emojis. Outros são atribuídos a ações que controlam como o texto ou os caracteres são exibidos, como avançar para uma nova linha. Muitos pontos de código ainda não foram atribuídos.

Aqui estão alguns exemplos de atribuições de pontos de código, com links para gráficos Unicode nos quais eles aparecem:

Decimal Hex Exemplo Descrição
10 U+000A N/A AVANÇO DE LINHA
97 U+0061 um LETRA PEQUENA LATINA A
562 U+0232 Ȳ LETRA MAIÚSCULA LATINA Y COM MACRON
68,675 U+10C43 𐱃 OLD TURKIC LETTER ORKHON AT
127,801 U+1F339 🌹 EMOJI DE ROSA

Os pontos de código são habitualmente referidos usando a sintaxe U+xxxx, onde xxxx é o valor inteiro codificado por hex.

Dentro da gama completa de pontos de código existem dois subintervalos:

  • O Plano Multilingue Básico (BMP) no intervalo U+0000..U+FFFF. Esta faixa de 16 bits fornece 65.536 pontos de código, o suficiente para cobrir a maioria dos sistemas de escrita do mundo.
  • Pontos de código suplementares no intervalo U+10000..U+10FFFF. Este intervalo de 21 bits fornece mais de um milhão de pontos de código adicionais que podem ser usados para idiomas menos conhecidos e outros fins, como emojis.

O diagrama a seguir ilustra a relação entre o BMP e os pontos de código suplementares.

BMP e pontos de código suplementares

Unidades de código UTF-16

UTF-16 (16-bit Unicode Transformation Format) é um sistema de codificação de caracteres que usa unidades de código de 16 bits para representar pontos de código Unicode. O .NET usa UTF-16 para codificar o texto em um string. Uma char instância representa uma unidade de código de 16 bits.

Uma única unidade de código de 16 bits pode representar qualquer ponto de código no intervalo de 16 bits do Plano Multilingue Básico. Mas para um ponto de código no intervalo suplementar, são necessárias duas instâncias char.

Pares substitutos

A tradução de dois valores de 16 bits para um único valor de 21 bits é facilitada por um intervalo especial chamado pontos de código substitutos, de U+D800 para U+DFFF (decimal 55.296 a 57.343), inclusive.

O diagrama a seguir ilustra a relação entre o BMP e os pontos de código substitutos.

BMP e pontos de código substitutos

Quando um ponto de código substituto alto (U+D800..U+DBFF) é imediatamente seguido por um ponto de código substituto baixo (U+DC00..U+DFFF), o par é interpretado como um ponto de código suplementar usando a seguinte fórmula:

code point = 0x10000 +
  ((high surrogate code point - 0xD800) * 0x0400) +
  (low surrogate code point - 0xDC00)

Aqui está a mesma fórmula usando notação decimal:

code point = 65,536 +
  ((high surrogate code point - 55,296) * 1,024) +
  (low surrogate code point - 56,320)

Um ponto de código substituto alto não tem um valor numérico maior do que um ponto de código substituto baixo . O ponto de código substituto alto é chamado de "alto" porque é usado para calcular os 10 bits de ordem superior de um intervalo de pontos de código de 20 bits. O ponto de código substituto baixo é usado para calcular os 10 bits de ordem inferior.

Por exemplo, o ponto de código real que corresponde ao par 0xD83C substituto e 0xDF39 é calculado da seguinte forma:

actual = 0x10000 + ((0xD83C - 0xD800) * 0x0400) + (0xDF39 - 0xDC00)
       = 0x10000 + (          0x003C  * 0x0400) +           0x0339
       = 0x10000 +                      0xF000  +           0x0339
       = 0x1F339

Aqui está o mesmo cálculo usando notação decimal:

actual =  65,536 + ((55,356 - 55,296) * 1,024) + (57,145 - 56320)
       =  65,536 + (              60  * 1,024) +             825
       =  65,536 +                     61,440  +             825
       = 127,801

O exemplo anterior demonstra que "\ud83c\udf39" é a codificação UTF-16 do U+1F339 ROSE ('🌹') ponto de código mencionado anteriormente.

Valores escalares Unicode

O termo valor escalar Unicode refere-se a todos os pontos de código exceto os pontos de código de substituição. Em outras palavras, um valor escalar é qualquer ponto de código ao qual é atribuído um caractere ou que pode ser atribuído um caractere no futuro. "Caractere" aqui refere-se a qualquer coisa que possa ser atribuída a um ponto de código, o que inclui coisas como ações que controlam como o texto ou os caracteres são exibidos.

O diagrama a seguir ilustra os pontos de código de valor escalar.

Valores escalares

O Rune tipo como um valor escalar

Importante

O Rune tipo não está disponível no .NET Framework.

No .NET, o System.Text.Rune tipo representa um valor escalar Unicode.

Os Rune construtores validam que a instância resultante é um valor escalar Unicode válido, caso contrário, eles lançam uma exceção. O exemplo a seguir mostra o código que instancia Rune instâncias com êxito porque a entrada representa valores escalares válidos:

Rune a = new Rune('a');
Rune b = new Rune(0x0061);
Rune c = new Rune('\u0061');
Rune d = new Rune(0x10421);
Rune e = new Rune('\ud801', '\udc21');

O exemplo a seguir lança uma exceção porque o ponto de código está no intervalo substituto e não faz parte de um par substituto:

Rune f = new Rune('\ud801');

O exemplo a seguir lança uma exceção porque o ponto de código está além do intervalo suplementar:

Rune g = new Rune(0x12345678);

Rune Exemplo de uso: alterar o caso das letras

Uma API que usa um char e assume que está a trabalhar com um ponto de código escalar não funciona corretamente se char for de um par substituto. Por exemplo, considere o seguinte método que chama Char.ToUpperInvariant em cada char de um string:

// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
static string ConvertToUpperBadExample(string input)
{
    StringBuilder builder = new StringBuilder(input.Length);
    for (int i = 0; i < input.Length; i++) /* or 'foreach' */
    {
        builder.Append(char.ToUpperInvariant(input[i]));
    }
    return builder.ToString();
}

Se o inputstring contiver a letra Deseret minúscula er (𐑉), este código não o converterá para maiúsculas (𐐡). O código chama char.ToUpperInvariant separadamente em cada ponto de código substituto U+D801 e U+DC49. Mas U+D801 não tem informação suficiente por si só para identificá-lo como uma letra minúscula, então char.ToUpperInvariant deixa-o em paz. Ele trata U+DC49 da mesma forma. O resultado é que o '𐑉' minúsculo no inputstring não é convertido em '𐐡' maiúsculo.

Aqui estão duas opções para converter corretamente um string em maiúsculo:

  • Chame String.ToUpperInvariant com a entrada string em vez de iterar char-por-char. O string.ToUpperInvariant método tem acesso a ambas as partes de cada par substituto, para que possa lidar com todos os pontos de código Unicode corretamente.

  • Itere através dos valores escalares Unicode como instâncias do tipo Rune em vez de instâncias do tipo char, como é demonstrado no exemplo a seguir. Como uma Rune instância é um valor escalar Unicode válido, ela pode ser passada para APIs que esperam operar em um valor escalar. Por exemplo, chamar Rune.ToUpperInvariant como mostrado no exemplo a seguir dá resultados corretos:

    static string ConvertToUpper(string input)
    {
        StringBuilder builder = new StringBuilder(input.Length);
        foreach (Rune rune in input.EnumerateRunes())
        {
            builder.Append(Rune.ToUpperInvariant(rune));
        }
        return builder.ToString();
    }
    

Outras Rune APIs

O Rune tipo expõe análogos de muitas das char APIs. Por exemplo, os seguintes métodos espelham APIs estáticas no tipo char

Para obter o valor escalar bruto de uma Rune instância, use a Rune.Value propriedade.

Para converter um Rune instância novamente para uma sequência de chars, use Rune.ToString ou o método Rune.EncodeToUtf16.

Como qualquer valor escalar Unicode é representável por um único char ou por um par substituto, qualquer Rune instância pode ser representada por, no máximo, 2 char instâncias. Use Rune.Utf16SequenceLength para ver quantas char instâncias são necessárias para representar uma Rune instância.

Para obter mais informações sobre o tipo .NETRune, consulte a Rune referência da API.

Aglomerados de grafemas

O que parece um caractere pode resultar de uma combinação de vários pontos de código, então um termo mais descritivo que é frequentemente usado no lugar de "caractere" é agrupamento de grafema. O termo equivalente em .NET é text element.

Considere as string instâncias "a", "á", "á" e "👩🏽‍🚒". Se o seu sistema operacional manipulá-los conforme especificado pelo padrão Unicode, cada uma dessas string instâncias aparecerá como um único elemento de texto ou cluster de grafemas. Mas os dois últimos são representados por mais de um ponto de código de valor escalar.

  • O string "a" é representado por um valor escalar e contém uma char instância.

    • U+0061 LATIN SMALL LETTER A
  • O string "á" é representado por um valor escalar e contém uma char instância.

    • U+00E1 LATIN SMALL LETTER A WITH ACUTE
  • O string "á" parece igual a "á", mas é representado por dois valores escalares e consiste em duas char instâncias.

    • U+0061 LATIN SMALL LETTER A
    • U+0301 COMBINING ACUTE ACCENT
  • Finalmente, o string "👩🏽‍🚒" é representado por quatro valores escalares e contém sete char instâncias.

    • U+1F469 WOMAN (gama suplementar, requer um par surrogado)
    • U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4 (gama suplementar, requer um par substituto)
    • U+200D ZERO WIDTH JOINER
    • U+1F692 FIRE ENGINE (gama suplementar, requer um par substituto)

Em alguns dos exemplos anteriores - como o modificador de acento combinado ou o modificador de tom de pele - o ponto de código não é exibido como um elemento autônomo na tela. Em vez disso, serve para modificar a aparência de um elemento de texto que veio antes dele. Esses exemplos mostram que podem ser necessários vários valores escalares para compor o que pensamos como um único "caractere" ou "cluster de grafemas".

Para enumerar os clusters de grafema de um string, use a StringInfo classe como mostrado no exemplo a seguir. Se estiveres familiarizado com Swift, o tipo StringInfo na .NET é conceitualmente semelhante ao tipo character de Swift.

Exemplo: instâncias de count char, Rune, e text

Em APIs .NET, um cluster de grafema é chamado de elemento de texto. O método a seguir demonstra as diferenças entre char, Rune, e instâncias de elemento de texto em um string:

static void PrintTextElementCount(string s)
{
    Console.WriteLine(s);
    Console.WriteLine($"Number of chars: {s.Length}");
    Console.WriteLine($"Number of runes: {s.EnumerateRunes().Count()}");

    TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(s);

    int textElementCount = 0;
    while (enumerator.MoveNext())
    {
        textElementCount++;
    }

    Console.WriteLine($"Number of text elements: {textElementCount}");
}
PrintTextElementCount("a");
// Number of chars: 1
// Number of runes: 1
// Number of text elements: 1

PrintTextElementCount("á");
// Number of chars: 2
// Number of runes: 2
// Number of text elements: 1

PrintTextElementCount("👩🏽‍🚒");
// Number of chars: 7
// Number of runes: 4
// Number of text elements: 1

Exemplo: dividir string instâncias

Ao dividir string instâncias, evite dividir pares substitutos e clusters de grafemas. Considere o seguinte exemplo de código incorreto, que pretende inserir quebras de linha a cada 10 caracteres em um string:

// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
static string InsertNewlinesEveryTencharsBadExample(string input)
{
    StringBuilder builder = new StringBuilder();

    // First, append chunks in multiples of 10 chars
    // followed by a newline.
    int i = 0;
    for (; i < input.Length - 10; i += 10)
    {
        builder.Append(input, i, 10);
        builder.AppendLine(); // newline
    }

    // Then append any leftover data followed by
    // a final newline.
    builder.Append(input, i, input.Length - i);
    builder.AppendLine(); // newline

    return builder.ToString();
}

Como esse código enumera instâncias, um par substituto char que passa por um limite de 10char será dividido e uma nova linha injetada entre eles. Essa inserção introduz corrupção de dados, porque os pontos de código substitutos são significativos apenas como pares.

O potencial de corrupção de dados não será eliminado se você enumerar Rune instâncias (valores escalares) em vez de char instâncias. Um conjunto de instâncias pode compor um cluster de grafema Rune que se estende por um limite de 10 char. Se o conjunto de clusters de grafema estiver dividido, não poderá ser interpretado corretamente.

Uma abordagem melhor é quebrar string contando clusters de grafema, ou elementos de texto, como no exemplo a seguir:

static string InsertNewlinesEveryTenTextElements(string input)
{
    StringBuilder builder = new StringBuilder();

    // Append chunks in multiples of 10 chars

    TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(input);

    int textElementCount = 1;
    while (enumerator.MoveNext())
    {
        builder.Append(enumerator.Current);
        if (textElementCount % 10 == 0)
        {
            builder.AppendLine(); // newline
        }
        textElementCount++;
    }

    // Add a final newline.
    builder.AppendLine(); // newline
    return builder.ToString();

}

Como observado anteriormente, antes do .NET 5, a classe tinha um bug fazendo com que alguns clusters de grafema StringInfo fossem manipulados incorretamente.

UTF-8 e UTF-32

As seções anteriores se concentraram no UTF-16 porque é isso que o .NET usa para codificar string instâncias. Existem outros sistemas de codificação para Unicode - UTF-8 e UTF-32. Essas codificações usam unidades de código de 8 bits e unidades de código de 32 bits, respectivamente.

Como UTF-16, UTF-8 requer várias unidades de código para representar alguns valores escalares Unicode. UTF-32 pode representar qualquer valor escalar em uma única unidade de código de 32 bits.

Aqui estão alguns exemplos mostrando como o mesmo ponto de código Unicode é representado em cada um desses três sistemas de codificação Unicode:

Scalar: U+0061 LATIN SMALL LETTER A ('a')
UTF-8 : [ 61 ]           (1x  8-bit code unit  = 8 bits total)
UTF-16: [ 0061 ]         (1x 16-bit code unit  = 16 bits total)
UTF-32: [ 00000061 ]     (1x 32-bit code unit  = 32 bits total)

Scalar: U+0429 CYRILLIC CAPITAL LETTER SHCHA ('Щ')
UTF-8 : [ D0 A9 ]        (2x  8-bit code units = 16 bits total)
UTF-16: [ 0429 ]         (1x 16-bit code unit  = 16 bits total)
UTF-32: [ 00000429 ]     (1x 32-bit code unit  = 32 bits total)

Scalar: U+A992 JAVANESE LETTER GA ('ꦒ')
UTF-8 : [ EA A6 92 ]     (3x  8-bit code units = 24 bits total)
UTF-16: [ A992 ]         (1x 16-bit code unit  = 16 bits total)
UTF-32: [ 0000A992 ]     (1x 32-bit code unit  = 32 bits total)

Scalar: U+104CC OSAGE CAPITAL LETTER TSHA ('𐓌')
UTF-8 : [ F0 90 93 8C ]  (4x  8-bit code units = 32 bits total)
UTF-16: [ D801 DCCC ]    (2x 16-bit code units = 32 bits total)
UTF-32: [ 000104CC ]     (1x 32-bit code unit  = 32 bits total)

Como observado anteriormente, uma única unidade de código UTF-16 de um par substituto não tem sentido por si só. Da mesma forma, uma única unidade de código UTF-8 não tem sentido por si só se estiver em uma sequência de dois, três ou quatro usados para calcular um valor escalar.

Nota

A partir do C# 11, você pode representar literais UTF-8 string usando o sufixo "u8" em um literal string. Para mais informações sobre literais UTF-8 string, consulte a seção "string literais" do artigo sobre tipos de referência integrados no Guia C#.

Endianness

No .NET, as unidades de código UTF-16 de um string são armazenadas na memória continuamente como uma sequência de instâncias de inteiros de 16 bits (char). Os bits de unidades de código individuais são dispostos de acordo com a endianness da arquitetura atual.

Em uma arquitetura little-endian, o string consistindo dos pontos de código UTF-16 [ D801 DCCC ] seria disposto na memória como os bytes [ 0x01, 0xD8, 0xCC, 0xDC ]. Em uma arquitetura big-endian, esse mesmo string seria disposto na memória como os bytes [ 0xD8, 0x01, 0xDC, 0xCC ].

Os sistemas informáticos que comunicam entre si devem chegar a acordo sobre a representação dos dados que atravessam o fio. A maioria dos protocolos de rede usa UTF-8 como um padrão ao transmitir texto, em parte para evitar problemas que podem resultar de uma máquina big-endian se comunicando com uma máquina little-endian. O string composto pelos pontos de código UTF-8 [ F0 90 93 8C ] será sempre representado pelos bytes [ 0xF0, 0x90, 0x93, 0x8C ], independentemente da ordem de byte.

Para usar UTF-8 para transmitir texto, os aplicativos .NET geralmente usam código como o exemplo a seguir:

string stringToWrite = GetString();
byte[] stringAsUtf8Bytes = Encoding.UTF8.GetBytes(stringToWrite);
await outputStream.WriteAsync(stringAsUtf8Bytes, 0, stringAsUtf8Bytes.Length);

No exemplo anterior, o método Encoding.UTF8.GetBytes decodifica o UTF-16 string de volta em uma série de valores escalares Unicode e, em seguida, recodifica esses valores escalares em UTF-8 e coloca a sequência resultante em uma byte matriz. O método Encoding.UTF8.GetString executa a transformação oposta, convertendo uma matriz UTF-8 byte em uma UTF-16 string.

Aviso

Como o UTF-8 é comum na internet, pode ser tentador ler bytes brutos do fio e tratar os dados como se fossem UTF-8. No entanto, você deve validar que ele está realmente bem formado. Um cliente mal-intencionado pode enviar UTF-8 mal formado para o seu serviço. Se você operar com esses dados como se estivessem bem formados, isso pode causar erros ou falhas de segurança em seu aplicativo. Para validar dados UTF-8, pode-se usar um método como Encoding.UTF8.GetString, que realizará a validação durante a conversão dos dados de entrada para um string.

Codificação bem formada

Uma codificação Unicode bem formada é uma string das unidades de código que podem ser decodificadas de forma inequívoca e sem erros em uma sequência de valores escalares Unicode. Dados bem formados podem ser transcodificados livremente entre UTF-8, UTF-16 e UTF-32.

A questão de saber se uma sequência de codificação é bem formada ou não não está relacionada com a endianidade da arquitetura de uma máquina. Uma sequência UTF-8 malformada é incorretamente formatada da mesma forma em máquinas big-endian e little-endian.

Aqui estão alguns exemplos de codificações mal formadas:

  • Em UTF-8, a sequência [ 6C C2 61 ] é mal formada porque C2 não pode ser seguida por 61.

  • Em UTF-16, a sequência [ DC00 DD00 ] (ou, em C#, o string"\udc00\udd00") é malformada porque o substituto baixo DC00 não pode ser seguido por outro substituto baixo DD00.

  • Em UTF-32, a sequência [ 0011ABCD ] é mal formada porque 0011ABCD está fora do intervalo de valores escalares Unicode.

No .NET, string as instâncias quase sempre contêm dados UTF-16 bem formados, mas isso não é garantido. Os exemplos a seguir mostram código C# válido que cria dados UTF-16 malformados em instâncias de string.

  • Um literal mal formado:

    const string s = "\ud800";
    
  • Uma substring que divide um par substituto:

    string x = "\ud83e\udd70"; // "🥰"
    string y = x.Substring(1, 1); // "\udd70" standalone low surrogate
    

APIs como Encoding.UTF8.GetString nunca retornam instâncias mal formadas de string. Encoding.GetString e Encoding.GetBytes métodos detetam sequências malformadas na entrada e executam a substituição de caracteres ao gerar a saída. Por exemplo, se Encoding.ASCII.GetString(byte[]) vir um byte não-ASCII na entrada (fora do intervalo U+0000..U+007F), ele insere um '?' na instância retornada string . Encoding.UTF8.GetString(byte[]) substitui sequências UTF-8 mal formadas por U+FFFD REPLACEMENT CHARACTER ('�') na instância retornada string . Para obter mais informações, consulte o padrão Unicode, Seções 5.22 e 3.9.

As classes internas Encoding também podem ser configuradas para, quando sequências mal formadas forem detectadas, lançar uma exceção em vez de executar a substituição de caracteres. Essa abordagem é frequentemente usada em aplicativos sensíveis à segurança, onde a substituição de caracteres pode não ser aceitável.

byte[] utf8Bytes = ReadFromNetwork();
UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
string asString = encoding.GetString(utf8Bytes); // will throw if 'utf8Bytes' is ill-formed

Para obter informações sobre como usar as classes internas, consulte Como usar classes de Encoding codificação de caracteres no .NET.

Consulte também