Compartilhar via


Utf8 Strings Literais

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de linguagem (LDM) .

Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .

Resumo

Esta proposta adiciona a capacidade de escrever literais de cadeia UTF8 em C# e codificá-los automaticamente em sua representação UTF-8 byte.

Motivação

UTF8 é a linguagem da web e o seu uso é necessário em partes significativas da stack .NET. Embora muitos dos dados sejam derivados da pilha de rede na forma de byte[], ainda existem usos significativos de constantes no código. Por exemplo, a pilha de rede tem que escrever normalmente constantes como "HTTP/1.0\r\n", " AUTH" ou . "Content-Length: ".

Hoje não há sintaxe eficiente para fazer isso, pois C# representa todas as cadeias de caracteres usando codificação UTF16. Isso significa que os desenvolvedores têm que escolher entre a conveniência da codificação em execução, que incorre em sobrecarga, incluindo o tempo gasto na inicialização realmente executando a operação de codificação (e alocações se visando um tipo que realmente não as exige), ou converter manualmente os bytes e armazenar em um byte[].

// Efficient but verbose and error prone
static ReadOnlySpan<byte> AuthWithTrailingSpace => new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
WriteBytes(AuthWithTrailingSpace);

// Incurs allocation and startup costs performing an encoding that could have been done at compile-time
static readonly byte[] s_authWithTrailingSpace = Encoding.UTF8.GetBytes("AUTH ");
WriteBytes(s_authWithTrailingSpace);

// Simplest / most convenient but terribly inefficient
WriteBytes(Encoding.UTF8.GetBytes("AUTH "));

Essa troca é um ponto problemático que surge com frequência para nossos parceiros no tempo de execução, ASP.NET e Azure. Muitas vezes, isso faz com que percam desempenho porque não querem passar pelo incômodo de escrever, à mão, a codificação byte[].

Para corrigir isso, vamos permitir literais UTF8 na linguagem e codificá-los para o byte[] UTF8 no momento da compilação.

Projeto detalhado

sufixo u8 em literais de cadeias

A linguagem fornecerá o sufixo u8 em literais de string para forçar o tipo a ser UTF8. O sufixo é insensível a maiúsculas e minúsculas, o sufixo U8 será suportado e terá o mesmo significado que o sufixo u8.

Quando o sufixo u8 é usado, o valor do literal é um ReadOnlySpan<byte> que contém uma representação em bytes UTF-8 de uma string. Um terminador nulo é colocado além do último byte na memória (e fora do comprimento do ReadOnlySpan<byte>) para lidar com alguns cenários de interoperabilidade em que a chamada espera cadeias de caracteres terminadas nulas.

string s1 = "hello"u8;             // Error
var s2 = "hello"u8;                // Okay and type is ReadOnlySpan<byte>
ReadOnlySpan<byte> s3 = "hello"u8; // Okay.
byte[] s4 = "hello"u8;             // Error - Cannot implicitly convert type 'System.ReadOnlySpan<byte>' to 'byte[]'.
byte[] s5 = "hello"u8.ToArray();   // Okay.
Span<byte> s6 = "hello"u8;         // Error - Cannot implicitly convert type 'System.ReadOnlySpan<byte>' to 'System.Span<byte>'.

Uma vez que os literais seriam alocados como constantes globais, o tempo de vida do ReadOnlySpan<byte> resultante não impediria que ele fosse devolvido ou passado para outro lugar. No entanto, certos contextos, principalmente dentro de funções assíncronas, não permitem variáveis locais de tipos ref struct, então haveria uma penalidade de uso nessas situações, sendo necessária uma chamada ToArray() ou similar.

Um literal u8 não tem um valor constante. Isso porque ReadOnlySpan<byte> não pode ser o tipo de constante hoje. Se a definição de const for expandida no futuro para considerar ReadOnlySpan<byte>, então esse valor também deve ser considerado uma constante. Na prática, porém, isso significa que um literal u8 não pode ser usado como o valor padrão de um parâmetro opcional.

// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing"u8) { ... } 

Quando o texto de entrada para o literal é uma cadeia de caracteres UTF16 malformada, o idioma emitirá um erro:

var bytes = "hello \uD8\uD8"u8; // Error: malformed UTF16 input string

var bytes2 = "hello \uD801\uD802"u8; // Allowed: invalid UTF16 values, but it's correctly formed.

Operador de adição

Um novo ponto de lista será adicionado ao §12.10.5 Operador de adição da seguinte forma.

  • Concatenação de bytes na representação UTF8:

    ReadOnlySpan<byte> operator +(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y);
    

    Este operador binário + executa a concatenação de sequências de bytes e aplica-se se e somente se ambos os operandos forem representações de bytes semanticamente UTF8. Um operando é, semânticamente, uma representação de bytes UTF-8 quando é o valor de um literal u8 ou um valor produzido pelo operador de concatenação da representação de bytes UTF-8.

    O resultado da concatenação da representação de bytes UTF8 é um ReadOnlySpan<byte> composto pelos bytes do operando esquerdo seguidos pelos bytes do operando direito. Um terminador nulo é colocado além do último byte na memória (e fora do comprimento do ReadOnlySpan<byte>) para lidar com alguns cenários de interoperabilidade em que a chamada espera cadeias de caracteres terminadas nulas.

Rebaixamento

A linguagem vai minimizar as cadeias de caracteres codificadas em UTF8 tal como se o desenvolvedor tivesse digitado o literal byte[] resultante no código. Por exemplo:

ReadOnlySpan<byte> span = "hello"u8;

// Equivalent to

ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 }).
                               Slice(0,5); // The `Slice` call will be optimized away by the compiler.

Isso significa que todas as otimizações que se aplicam ao formulário new byte[] { ... } também se aplicarão aos literais utf8. Isso significa que o local de chamada não exigirá alocação, pois o C# otimizará isso para ser armazenado na seção .data do arquivo PE.

Várias aplicações consecutivas de operadores de concatenação de representação de bytes UTF8 são reduzidas numa única criação de ReadOnlySpan<byte> com uma matriz de bytes contendo a sequência de bytes final.

ReadOnlySpan<byte> span = "h"u8 + "el"u8 + "lo"u8;

// Equivalent to

ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00 }).
                               Slice(0,5); // The `Slice` call will be optimized away by the compiler.

Desvantagens

Confiando nas APIs principais

A implementação do compilador usará UTF8Encoding tanto para deteção de string inválida quanto para tradução para byte[]. As APIs exatas possivelmente dependerão de qual estrutura de destino o compilador está usando. Mas UTF8Encoding será o cavalo de batalha da implementação.

Historicamente, o compilador tem evitado usar APIs de tempo de execução para processamento literal. Isso ocorre porque ele assume o controle de como as constantes são processadas longe da linguagem e no tempo de execução. Concretamente, isso significa que itens como correções de bugs podem alterar a codificação constante e significa que o resultado da compilação em C# depende de qual tempo de execução o compilador está executando.

Não se trata de um problema hipotético. As primeiras versões do Roslyn usavam double.Parse para lidar com a análise de constantes de ponto flutuante. Isso causou uma série de problemas. Primeiro, isso significava que alguns valores de ponto flutuante tinham representações diferentes entre o compilador nativo e Roslyn. Em segundo lugar, à medida que o núcleo .NET evoluía e corrigia bugs de longa data no código double.Parse, isso significava que o significado dessas constantes mudava na linguagem, dependendo do tempo de execução em que o compilador executava. Como resultado, o compilador acabou escrevendo sua própria versão do código de análise de ponto flutuante e removendo a dependência de double.Parse.

Este cenário foi discutido com a equipa de tempo de execução e não sentimos que tenha os mesmos problemas que tínhamos enfrentado antes. A análise UTF8 é estável em todos os tempos de execução e não há problemas conhecidos nesta área que sejam áreas para preocupações futuras de compatação. Se surgir, podemos reavaliar a estratégia.

Alternativas

Apenas tipo de alvo

O design pode depender apenas do "target typing" e remover o sufixo u8 em literais string. Na maioria dos casos atualmente, o string literal está sendo atribuído diretamente a um ReadOnlySpan<byte>, portanto, é desnecessário.

ReadOnlySpan<byte> span = "Hello World;" 

O sufixo u8 existe principalmente para suportar dois cenários: var e resolução de sobrecarga. Para este último, considere o seguinte caso de uso:

void Write(ReadOnlySpan<byte> span) { ... } 
void Write(string s) {
    var bytes = Encoding.UTF8.GetBytes(s);
    Write(bytes.AsSpan());
}

Dada a implementação, é melhor chamar Write(ReadOnlySpan<byte>) e o sufixo u8 torna isso conveniente: Write("hello"u8). Na falta disso, os desenvolvedores precisam recorrer a Write((ReadOnlySpan<byte>)"hello")de casting desajeitadas.

Ainda assim, este é um item de conveniência, o recurso pode existir sem ele e não causa interrupções adicioná-lo num momento posterior.

Aguarde o tipo Utf8String

Embora o ecossistema .NET esteja padronizando o ReadOnlySpan<byte> como o tipo de cadeia de caracteres Utf8 de fato hoje, é possível que o runtime introduza um tipo real de Utf8String no futuro.

Devemos avaliar nosso projeto aqui diante dessa possível mudança e refletir se nos arrependeríamos das decisões que tomamos. Isso deve ser ponderado em relação à probabilidade realista de introduzirmos Utf8String, uma probabilidade que parece diminuir a cada dia em que encontramos ReadOnlySpan<byte> como uma alternativa aceitável.

Parece improvável que venhamos a nos arrepender da conversão do tipo alvo entre literais de strings e ReadOnlySpan<byte>. O uso de ReadOnlySpan<byte> como utf8 está incorporado em nossas APIs agora e, portanto, ainda há valor na conversão, mesmo que Utf8String apareça e seja um tipo "melhor". O idioma poderia simplesmente preferir conversões a Utf8String em vez de ReadOnlySpan<byte>.

Parece mais provável que nos arrependamos do sufixo u8 apontando para ReadOnlySpan<byte> em vez de Utf8String. Seria semelhante à forma como lamentamos que stackalloc int[] tenha um tipo natural de int* em vez de Span<int>. No entanto, não é um fator decisivo, apenas um inconveniente.

Conversões entre constantes de string e sequências byte

As conversões nesta seção não foram implementadas. Estas conversões continuam a ser propostas ativas.

Esta linguagem permitirá conversões entre constantes string e sequências byte onde o texto é convertido em bytes UTF8 equivalentes. Especificamente, o compilador permitirá string_constant_to_UTF8_byte_representation_conversion - conversões implícitas de constantes de string para byte[], Span<byte>e ReadOnlySpan<byte>. Um novo ponto será adicionado na secção de conversões implícitas §10.2. Esta conversão não é uma conversão padrão §10.4.

byte[] array = "hello";             // new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f }
Span<byte> span = "dog";            // new byte[] { 0x64, 0x6f, 0x67 }
ReadOnlySpan<byte> span = "cat";    // new byte[] { 0x63, 0x61, 0x74 }

Quando o texto de entrada para a conversão é uma string UTF16 malformada, o idioma emitirá um erro:

const string text = "hello \uD801\uD802";
byte[] bytes = text; // Error: the input string is not valid UTF16

Espera-se que o uso predominante deste recurso seja com literais, mas o recurso funcionará com qualquer valor constante string. A conversão da constante string com o valor null também será suportada. O resultado da conversão será default valor do tipo de destino.

const string data = "dog"
ReadOnlySpan<byte> span = data;     // new byte[] { 0x64, 0x6f, 0x67 }

No caso de qualquer operação constante em strings, como +, a codificação para UTF8 ocorrerá no string final, em vez de acontecer primeiro para as partes individuais e depois concatenar os resultados. Essa ordenação é importante considerar porque pode afetar se a conversão é bem-sucedida ou não.

const string first = "\uD83D";  // high surrogate
const string second = "\uDE00"; // low surrogate
ReadOnlySpan<byte> span = first + second;

As duas partes aqui são inválidas por si só, pois são partes incompletas de um par substituto. Individualmente não há tradução correta para UTF8, mas juntos eles formam um par substituto completo que pode ser traduzido com sucesso para UTF8.

A string_constant_to_UTF8_byte_representation_conversion não é permitida nas Árvores de Expressão Linq.

Embora as entradas para essas conversões sejam constantes e os dados sejam totalmente codificados em tempo de compilação, a conversão não é considerada constante pela linguagem. Isso ocorre porque as matrizes não são constantes hoje. Se a definição de const for expandida no futuro para considerar matrizes, essas conversões também devem ser consideradas. Na prática, porém, isso significa que um resultado dessas conversões não pode ser usado como o valor padrão de um parâmetro opcional.

// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing") { ... } 

Uma vez implementados, os literais de cadeia de caracteres terão o mesmo problema que outros literais têm na linguagem: que tipo eles representam depende de como eles são usados. C# fornece um sufixo literal para desambiguar o significado de outros literais. Por exemplo, os desenvolvedores podem escrever 3.14f para forçar o valor a ser um float ou 1l para forçar o valor a ser um long.

Questões por resolver

As três primeiras perguntas de design estão relacionadas a conversões de cadeia de caracteres para Span<byte> / ReadOnlySpan<byte>. Elas não foram implementadas.

(Resolvido) Conversões entre uma constante de string com valor null e sequências byte

Não é especificado se essa conversão é suportada e, em caso afirmativo, como ela é executada.

Proposta:

Permita conversões implícitas de uma constante de string com valor null para byte[], Span<byte>e ReadOnlySpan<byte>. O resultado da conversão é default valor do tipo de destino.

Resolução:

A proposta é aprovada - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversions-from-null-literals.

(Resolvido) A que lugar string_constant_to_UTF8_byte_representation_conversion pertence?

É string_constant_to_UTF8_byte_representation_conversion um ponto nas conversões implícitas §10.2 seção por si só, ou faz parte de §10.2.11, ou pertence a algum outro grupo de conversões implícitas existente?

Proposta:

É um novo marcador em conversões implícitas §10.2, semelhante a "Conversões de cadeia de caracteres interpoladas implícitas" ou "Conversões de grupo de método". Não parece pertencer a "conversões implícitas de expressão constante" porque, embora a fonte seja uma expressão constante, o resultado nunca é uma expressão constante. Além disso, "conversões implícitas de expressão constante" são consideradas "conversões implícitas padrão" §10.4.2, o que provavelmente levará a mudanças de comportamento não triviais envolvendo conversões definidas pelo usuário.

Resolução:

Vamos introduzir um novo tipo de conversão para constante de string para bytes UTF-8 - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-kinds

(Resolvido) A conversão string_constant_to_UTF8_byte_representation_conversion é uma conversão padrão?

Além das conversões padrão "puras" (as conversões padrão são aquelas conversões predefinidas que podem ocorrer como parte de uma conversão definida pelo usuário), o compilador também trata algumas conversões predefinidas como padrão "um pouco". Por exemplo, uma conversão de cadeia de caracteres interpolada implícita pode ocorrer como parte de uma conversão definida pelo usuário se houver uma conversão explícita para o tipo de destino no código. Como se fosse uma conversão explícita padrão, mesmo que seja uma conversão implícita não incluída explicitamente no conjunto de conversões implícitas ou explícitas padrão. Por exemplo:

class C
{
    static void Main()
    {
        C1 x = $"hello"; // error CS0266: Cannot implicitly convert type 'string' to 'C1'. An explicit conversion exists (are you missing a cast?)
        var y = (C1)$"dog"; // works
    }
}

class C1
{
    public static implicit operator C1(System.FormattableString x) => new C1();
}

Proposta:

A nova conversão não é uma conversão padrão. Isso evitará alterações de comportamento não triviais envolvendo conversões definidas pelo usuário. Por exemplo, não precisaremos nos preocupar com cinversões definidas pelo usuário sob conversões literais de tupla implícitas, etc.

Resolução:

Não é uma conversão padrão, por enquanto - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#implicit-standard-conversion.

(Resolvido) Conversão de árvore de expressão Linq

Deveria a string_constant_to_UTF8_byte_representation_conversion ser permitida no contexto de uma conversão de Árvore de Expressão Linq? Podemos proibi-lo por enquanto, ou podemos simplesmente incluir a forma "rebaixada" na árvore. Por exemplo:

Expression<Func<byte[]>> x = () => "hello";           // () => new [] {104, 101, 108, 108, 111}
Expression<FuncSpanOfByte> y = () => "dog";           // () => new Span`1(new [] {100, 111, 103}) 
Expression<FuncReadOnlySpanOfByte> z = () => "cat";   // () => new ReadOnlySpan`1(new [] {99, 97, 116})

E quanto aos literais de cadeia de caracteres com o sufixo u8? Poderíamos apresentá-los como criações de matriz de bytes:

Expression<Func<byte[]>> x = () => "hello"u8;           // () => new [] {104, 101, 108, 108, 111}

Resolução:

Não permitido em Árvores de Expressão LINQ - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#expression-tree-representation.

(Resolvido) O tipo natural de um literal de cadeia de caracteres com sufixo u8

A seção "Projeto detalhado" diz: "O tipo natural, porém, será ReadOnlySpan<byte>." Ao mesmo tempo: "Quando o sufixo u8 é usado, o literal ainda pode ser convertido para qualquer um dos tipos permitidos: byte[], Span<byte> ou ReadOnlySpan<byte>."

Esta abordagem apresenta várias desvantagens:

  • ReadOnlySpan<byte> não está disponível no Desktop Framework;
  • Não existem conversões de ReadOnlySpan<byte> para byte[] ou Span<byte>. A fim de apoiá-los, provavelmente precisaremos tratar os literais como alvo digitado. Tanto as regras linguísticas como a aplicação tornar-se-ão mais complicadas.

Proposta:

O tipo natural será byte[]. Está prontamente disponível em todas as plataformas. A propósito, em tempo de execução, sempre começamos com a criação de uma matriz de bytes, mesmo com a proposta original em mente. Também não precisamos de regras de conversão especiais para suportar conversões para Span<byte> e ReadOnlySpan<byte>. Já existem conversões implícitas definidas pelo usuário de byte[] para Span<byte> e ReadOnlySpan<byte>. Há até mesmo uma conversão implícita definida pelo usuário para ReadOnlyMemory<byte> (veja a pergunta "Profundidade da conversão" abaixo). Há uma desvantagem, a linguagem não permite encadear conversões definidas pelo usuário. Assim, o código a seguir não será compilado:

using System;
class C
{
    static void Main()
    {
        var y = (C2)"dog"u8; // error CS0030: Cannot convert type 'byte[]' to 'C2'
        var z = (C3)"cat"u8; // error CS0030: Cannot convert type 'byte[]' to 'C3'
    }
}

class C2
{
    public static implicit operator C2(Span<byte> x) => new C2();
}

class C3
{
    public static explicit operator C3(ReadOnlySpan<byte> x) => new C3();
}

No entanto, como em qualquer conversão definida pelo usuário, uma conversão explícita pode ser usada para incorporar uma conversão definida pelo usuário em outra.

Parece que todos os cenários motivadores serão abordados com byte[] como o tipo natural, mas as regras de linguagem e implementação serão significativamente mais simples.

Resolução:

A proposta é aprovada - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#natural-type-of-u8-literals. Provavelmente vamos querer ter um debate mais profundo sobre se os literais de string u8 devem ter um tipo de array mutável, mas não achamos que esse debate seja necessário por enquanto.

Apenas o operador de conversão explícito foi implementado.

(Resolvido) Profundidade da conversão

Funcionará também em qualquer lugar onde um byte[] possa ser usado? Considere:

static readonly ReadOnlyMemory<byte> s_data1 = "Data"u8;
static readonly ReadOnlyMemory<byte> s_data2 = "Data";

O primeiro exemplo provavelmente deve funcionar por causa do tipo natural que vem de u8.

O segundo exemplo é difícil de fazer funcionar porque requer conversões em ambas as direções. Isso a menos que adicionemos ReadOnlyMemory<byte> como um dos tipos de conversão permitidos.

Proposta:

Não faça nada de especial.

Resolução:

Nenhuma nova meta de conversão foi adicionada por enquanto https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-depth. Nenhuma das conversões é compilada com sucesso.

(Resolvido) Quebras de resolução de sobrecarga

A seguinte API tornar-se-ia ambígua:

M("");
static void M1(ReadOnlySpan<char> charArray) => ...;
static void M1(byte[] byteArray) => ...;

O que devemos fazer para resolver este problema?

Proposta:

Semelhante a https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#overload-resolution, o membro da função Better (§11.6.4.3) é atualizado para preferir aqueles membros em que nenhuma das conversões envolvidas exige a conversão de constantes string em sequências UTF8 byte.

Membro de função melhor

... Dada uma lista de argumentos A com um conjunto de expressões de argumento {E1, E2, ..., En} e dois membros de função aplicáveis Mp e Mq com tipos de parâmetros {P1, P2, ..., Pn} e {Q1, Q2, ..., Qn}, Mp é definido como um membro de função melhor do que Mq se

  1. para cada argumento, a conversão implícita de Ex para Px não é uma string_constant_to_UTF8_byte_representation_conversion, e para pelo menos um argumento, a conversão implícita de Ex para Qx é uma string_constant_to_UTF8_byte_representation_conversion, ou
  2. para cada argumento, a conversão implícita de Ex para Px não é uma function_type_conversion, e
    • Mp é um método não genérico ou Mp é um método genérico com parâmetros de tipo {X1, X2, ..., Xp} e para cada parâmetro de tipo Xi o argumento de tipo é inferido de uma expressão ou de um tipo diferente de uma function_type, e
    • para pelo menos um argumento, a conversão implícita de Ex para Qx é um function_type_conversion, ou Mq é um método genérico com parâmetros de tipo {Y1, Y2, ..., Yq} e para pelo menos um parâmetro de tipo Yi o argumento de tipo é inferido a partir de um function_type, ou
  3. Para cada argumento, a conversão implícita de Ex para Qx não é melhor do que a conversão implícita de Ex para Px, e para pelo menos um argumento, a conversão de Ex para Px é melhor do que a conversão de Ex para Qx.

Note que a adição desta regra não cobrirá cenários em que métodos de instância se tornem aplicáveis e ofusquem métodos de extensão. Por exemplo:

using System;

class Program
{
    static void Main()
    {
        var p = new Program();
        Console.WriteLine(p.M(""));
    }

    public string M(byte[] b) => "byte[]";
}

static class E
{
    public static string M(this object o, string s) => "string";
}

O comportamento desse código mudará silenciosamente de impressão de "string" para impressão de "byte[]".

Estamos bem com essa mudança de comportamento? Deve ser documentado como uma alteração crítica?

Observe que não há nenhuma proposta para tornar string_constant_to_UTF8_byte_representation_conversion indisponível quando a versão de linguagem C#10 é direcionada. Nesse caso, o exemplo acima se torna um erro em vez de retornar ao comportamento C#10. Isso segue um princípio geral de que a versão do idioma de destino não afeta a semântica do idioma.

Estamos bem com esse comportamento? Deve ser documentado como uma alteração crítica?

A nova regra também não vai impedir quebras envolvendo conversões literais de tupla. Por exemplo

class C
{
    static void Main()
    {
        System.Console.Write(Test(("s", 1)));
    }

    static string Test((object, int) a) => "object";
    static string Test((byte[], int) a) => "array";
}

vai imprimir silenciosamente "matriz" em vez de "objeto".

Estamos bem com esse comportamento? Deve ser documentado como uma alteração crítica? Talvez pudéssemos complicar a nova regra para cavar as conversões literais de tupla.

Resolução:

O protótipo não vai ajustar nenhuma regra aqui, então podemos esperar ver o que quebra na prática - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#breaking-changes.

(Resolvido) O sufixo u8 deve ser insensível a maiúsculas e minúsculas?

Proposta:

Suportar também o sufixo U8 para consistência com sufixos numéricos.

Resolução:

Aprovado - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#suffix-case-sensitivity.

Exemplos hoje

Exemplos de onde o tempo de execução codificou manualmente os bytes UTF-8 hoje

Exemplos em que deixamos perf em cima da mesa

Reuniões de design

https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-18.md https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-06-06.md