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 do que um leitor percebe como um único elemento de exibiçã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.

Os string e char tipos

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. O string. A propriedade Length retorna o número de char instâncias na string instância.

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 char ocorrências 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 Description
10 U+000A N/A ALIMENTAÇÃO DE LINHA
97 U+0061 a LETRA PEQUENA LATINA A
562 U+0232 Ȳ LETRA MAIÚSCULA LATINA Y COM MACRON
68,675 U+10C43 𐱃 OLD TURKIC LETTER ORKHON NA
127,801 U+1F339 🌹 EMOJI ROSE

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 stringarquivo . 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, duas char instâncias são necessárias.

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 diferentes dos pontos de código substitutos. 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 maiúsculas e minúsculas

Uma API que usa um char e assume que está trabalhando com um ponto de código que é um valor escalar não funciona corretamente se o for de um par substituto char . Por exemplo, considere o seguinte método que chama Char.ToUpperInvariant cada char um em 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 er Minúscula Deseret (𐑉), este código não o converterá em maiúsculas (𐐡). O código chama char.ToUpperInvariant separadamente em cada ponto U+D801 de código substituto 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. E lida U+DC49 da mesma forma. O resultado é que o inputstring "minúsculo" não é convertido em maiúsculo.

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

  • Chame String.ToUpperInvariant 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 Rune instâncias em vez de char instâncias, como mostrado 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 char tipo:

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

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

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 referência da Rune 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 no .NET é elemento text.

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 o mesmo que "á", mas é representado por dois valores escalares e contém 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 substituto)
    • 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 você estiver familiarizado com o Swift, o tipo .NET StringInfo é conceitualmente semelhante ao tipo do character Swift.

Exemplo: ocorrências de elementos count char, Runee 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 Rune grafema que se estende por um limite de 10char . Se o conjunto de clusters de grafema estiver dividido, não poderá ser interpretado corretamente.

Uma abordagem melhor é quebrar a contagem de clusters de grafema string , 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 && textElementCount > 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 obter mais informações sobre literais UTF-8 string , consulte a seção "string literais" do artigo sobre tipos de referência incorporados no Guia C#.

Dianness

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

Em uma arquitetura little-endian, a string consistência dos pontos [ D801 DCCC ] de código UTF-16 seria disposta 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. A string consistência dos pontos [ F0 90 93 8C ] de código UTF-8 será sempre representada como os bytes [ 0xF0, 0x90, 0x93, 0x8C ] , independentemente da endianness.

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, você pode usar um método como Encoding.UTF8.GetString, que executará a validação durante a conversão dos dados de entrada em um stringarquivo .

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 erro 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 mal formada é mal formada 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 ) é mal formada porque o substituto DC00 baixo não pode ser seguido por outro substituto string"\udc00\udd00"DD00baixo.

  • 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 mal formados em string instâncias.

  • 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 retornar instâncias mal formadas string . Encoding.GetString e métodos detetam Encoding.GetBytes sequências mal formadas 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 lançar uma exceção em vez de executar a substituição de caracteres quando sequências mal formadas são vistas. 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