Literales de cadena Utf8
Nota
Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos e se incorporan en la especificación ECMA actual.
Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.
Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C# en el artículo sobre las especificaciones de .
Resumen
Esta propuesta agrega la capacidad de escribir literales de cadena UTF8 en C# y hacer que se codifiquen automáticamente en su representación byte
UTF-8.
Motivación
UTF8 es el formato de codificación de la web y su uso es necesario en partes significativas del entorno .NET. Aunque gran parte de los datos vienen en forma de byte[]
fuera de la pila de red todavía hay usos significativos de constantes en el código. Por ejemplo la pila de red tiene que escribir comúnmente constantes como "HTTP/1.0\r\n"
, " AUTH"
o . "Content-Length: "
.
En la actualidad, no hay ninguna sintaxis eficaz para hacerlo, ya que C# representa todas las cadenas que usan codificación UTF16. Esto significa que los desarrolladores tienen que elegir entre la comodidad de codificar en tiempo de ejecución, lo que conlleva una sobrecarga, incluido el tiempo empleado en el inicio al realizar efectivamente la operación de codificación (y las asignaciones si tienen como objetivo un tipo que realmente no las requiere), o traducir manualmente los bytes y almacenarlos en un 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 "));
Esta compensación es un punto problemático que surge con frecuencia para nuestros socios en el tiempo de ejecución, ASP.NET y Azure. A menudo les hace dejar el rendimiento sobre la mesa porque no quieren pasar por la molestia de escribir el encoding byte[]
a mano.
Para corregir esto, permitiremos literales UTF8 en el lenguaje y los codificaremos en UTF8 byte[]
en tiempo de compilación.
Diseño detallado
u8
sufijo en literales de cadena
El lenguaje proporcionará el sufijo u8
en los literales de cadena para forzar que el tipo sea UTF8.
El sufijo no distingue mayúsculas de minúsculas, se admitirá el sufijo U8
y tendrá el mismo significado que el sufijo u8
.
Cuando se usa el sufijo u8
, el valor del literal es un ReadOnlySpan<byte>
que contiene una representación de bytes UTF-8 de la cadena.
Un terminador NULL se coloca más allá del último byte en la memoria (y fuera de la longitud del ReadOnlySpan<byte>
) para controlar algunos escenarios de interoperabilidad en los que la llamada espera cadenas terminadas en NULL.
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>'.
Dado que los literales se asignarían como constantes globales, el tiempo de vida ReadOnlySpan<byte>
de la resultante no impediría que fuera devuelta o pasada a otro lugar. Sin embargo, ciertos contextos, más notablemente dentro de funciones async, no permiten locales de tipos ref struct, por lo que habría una penalización de uso en esas situaciones, siendo necesaria una llamada o similar ToArray()
.
Un literal de u8
no tiene un valor constante. Esto se debe a que ReadOnlySpan<byte>
actualmente no puede ser el tipo de una constante. Si la definición de const
se expande en el futuro para considerar ReadOnlySpan<byte>
, este valor también debe considerarse una constante. Prácticamente, esto significa que no se puede usar un literal de u8
como valor predeterminado de un parámetro opcional.
// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing"u8) { ... }
Cuando el texto de entrada del literal es una cadena UTF16 con formato incorrecto, el idioma emitirá un error:
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 suma
Se añadirá un nuevo punto a §12.10.5 Operador de adición como sigue.
Concatenación de representación de bytes UTF8:
ReadOnlySpan<byte> operator +(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y);
Este operador binario
+
realiza la concatenación de secuencias de bytes y es aplicable si y solo si ambos operandos son representaciones de bytes UTF8 semánticamente. Un operando es semánticamente una representación de bytes UTF8 cuando es un valor de un literalu8
o un valor producido por el operador de concatenación de representación de bytes UTF8.El resultado de la concatenación de representaciones de bytes UTF8 es un
ReadOnlySpan<byte>
que consta de los bytes del operando izquierdo seguidos de los bytes del operando derecho. Un terminador NULL se coloca más allá del último byte en la memoria (y fuera de la longitud delReadOnlySpan<byte>
) para controlar algunos escenarios de interoperabilidad en los que la llamada espera cadenas terminadas en NULL.
Reducción
El lenguaje bajará las cadenas con codificación UTF8 exactamente como si el desarrollador hubiera escrito el literal byte[]
resultante en el código. Por ejemplo:
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.
Esto significa que todas las optimizaciones que se aplican al formulario de new byte[] { ... }
también se aplicarán a literales utf8. Esto significa que el punto de llamada no requerirá asignación, ya que C# lo optimiza para almacenarlo en la sección .data
del archivo PE.
Múltiples aplicaciones consecutivas de operadores de concatenación de representación de bytes UTF8 se colapsan en una única creación de ReadOnlySpan<byte>
con matriz de bytes que contiene la secuencia final de bytes.
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.
Inconvenientes
Confiar en las API principales
La implementación del compilador usará UTF8Encoding
tanto para la detección de cadenas no válidas como para la traducción a byte[]
. Las API exactas dependerán posiblemente de la plataforma de destino que use el compilador. Pero UTF8Encoding
será el caballo de batalla de la implementación.
Históricamente, el compilador ha evitado el uso de API en tiempo de ejecución para el procesamiento literal. Esto se debe a que toma el control de cómo se procesan las constantes fuera del lenguaje y en el tiempo de ejecución. Concretamente, significa que los elementos como las correcciones de errores pueden cambiar la codificación constante y significan que el resultado de la compilación de C# depende del tiempo de ejecución en el que se ejecuta el compilador.
Esto no es un problema hipotético. Las versiones anteriores de Roslyn usaban double.Parse
para controlar el análisis de constantes de punto flotante. Esto provocó una serie de problemas. En primer lugar, significaba que algunos valores de punto flotante tenían representaciones diferentes entre el compilador nativo y Roslyn. En segundo lugar, como .NET Core evolucionó y corrigió errores de larga duración en el código double.Parse
significaba que el significado de esas constantes cambió en el lenguaje en función del tiempo de ejecución en el que se ejecutó el compilador. Como resultado, el compilador terminó escribiendo su propia versión del código de análisis de punto flotante y quitando la dependencia de double.Parse
.
Este escenario fue discutido con el equipo de tiempo de ejecución y no creemos que tenga los mismos problemas que hemos encontrado antes. El análisis de UTF8 es estable en los entornos de ejecución y no hay problemas conocidos en esta área que puedan ser motivo de preocupaciones de compatibilidad en el futuro. Si aparece, podemos volver a evaluar la estrategia.
Alternativas
Solo tipo de destino
El diseño podría basarse únicamente en la tipificación de destino y eliminar el sufijo u8
en los literales string
. Hoy en día, en la mayoría de los casos, el literal string
se asigna directamente a un objeto ReadOnlySpan<byte>
, por lo que es innecesario.
ReadOnlySpan<byte> span = "Hello World;"
El sufijo u8
existe principalmente para admitir dos escenarios: var
y resolución de sobrecargas. Para este último, considere el siguiente caso de uso:
void Write(ReadOnlySpan<byte> span) { ... }
void Write(string s) {
var bytes = Encoding.UTF8.GetBytes(s);
Write(bytes.AsSpan());
}
Dada la implementación, es mejor llamar a Write(ReadOnlySpan<byte>)
y el sufijo u8
lo hace conveniente: Write("hello"u8)
. Falta que los desarrolladores necesiten recurrir a una conversión complicada Write((ReadOnlySpan<byte>)"hello")
.
Aún así, esto es un elemento de comodidad, la característica puede existir sin él y puede añadirse sin problemas más adelante.
Esperar al tipo Utf8String
Mientras que el ecosistema .NET se está estandarizando ReadOnlySpan<byte>
como el tipo de cadena Utf8 de facto hoy en día, es posible que el tiempo de ejecución introduzca un tipo real Utf8String
en el futuro.
Debemos evaluar nuestro diseño aquí en la cara de este posible cambio y reflexionar sobre si lamentaremos las decisiones que hemos tomado. Esto debe sopesarse frente a la probabilidad realista de que introduzcamos Utf8String
, una probabilidad que parece disminuir cada día que encontramos ReadOnlySpan<byte>
como una alternativa aceptable.
Parece poco probable que lamentar la conversión de tipo de destino entre los literales de cadena y ReadOnlySpan<byte>
. El uso de ReadOnlySpan<byte>
como utf8 está incrustado en nuestras APIs ahora y, por lo tanto, todavía hay valor en la conversión incluso si Utf8String
aparece y es un tipo "mejor". El idioma podría simplemente preferir conversiones a Utf8String
sobre ReadOnlySpan<byte>
.
Parece más probable que lamentaremos el sufijo u8
que apunta a ReadOnlySpan<byte>
en lugar de Utf8String
. Sería similar a que lamentamos que stackalloc int[]
tenga un tipo natural de int*
en lugar de Span<int>
. Sin embargo, esto no es un obstáculo decisivo, sólo es un inconveniente.
Conversiones entre constantes de string
y secuencias de byte
No se han implementado las conversiones de esta sección. Estas conversiones siguen siendo propuestas activas.
El idioma permitirá conversiones entre constantes y secuencias de string
y byte
donde el texto se convierte en su representación equivalente en bytes UTF8. En concreto, el compilador permitirá string_constant_to_UTF8_byte_representation_conversion - conversiones implícitas de constantes string
a byte[]
, Span<byte>
, y ReadOnlySpan<byte>
.
Se añadirá un nuevo punto a la sección de conversiones implícitas §10.2. Esta conversión no es una conversión estándar §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 }
Cuando el texto de entrada de la conversión es una cadena UTF16 con formato incorrecto, el idioma emitirá un error:
const string text = "hello \uD801\uD802";
byte[] bytes = text; // Error: the input string is not valid UTF16
Se espera que el uso predominante de esta función sea con literales, pero funcionará con cualquier valor constante string
.
También se admitirá la conversión de una constante string
con valor null
. El resultado de la conversión será el valor default
del tipo de destino.
const string data = "dog"
ReadOnlySpan<byte> span = data; // new byte[] { 0x64, 0x6f, 0x67 }
En el caso de cualquier operación constante sobre cadenas, como +
, el encoding a UTF8 se producirá en el final string
frente a pasar por las partes individuales y luego concatenar los resultados. Este orden es importante tener en cuenta porque puede afectar a si la conversión se realiza correctamente o no.
const string first = "\uD83D"; // high surrogate
const string second = "\uDE00"; // low surrogate
ReadOnlySpan<byte> span = first + second;
Las dos partes aquí no son válidas por sí mismas, ya que son partes incompletas de un par suplente. Individualmente no hay ninguna traducción correcta a UTF8, pero juntas forman un par suplente completo que se puede traducir correctamente a UTF8.
La string_constant_to_UTF8_byte_representation_conversion no está permitida en los árboles de expresiones Linq.
Aunque las entradas de estas conversiones son constantes y los datos están totalmente codificados en tiempo de compilación, el lenguaje no considera constante la conversión. Esto se debe a que las matrices no son constantes en la actualidad. Si la definición de const
se expande en el futuro para tener en cuenta las matrices, también se deben tener en cuenta estas conversiones. Prácticamente, esto significa que no se puede usar un resultado de estas conversiones como valor predeterminado de un parámetro opcional.
// Error: The argument is not constant
void Write(ReadOnlySpan<byte> message = "missing") { ... }
Una vez implementados los literales de cadena tendrá el mismo problema que otros literales tienen en el lenguaje: el tipo que representan depende de cómo se usen. C# proporciona un sufijo literal para desambiguar el significado para otros literales. Por ejemplo, los desarrolladores pueden escribir 3.14f
para forzar que el valor sea un float
o 1l
para forzar que el valor sea un long
.
Preguntas sin resolver
Las tres primeras preguntas de diseño se relacionan con las conversiones de cadena a Span<byte>
, / yReadOnlySpan<byte>
. No se han implementado.
(Resuelto) Conversión entre una constante string
con el valor de null
y secuencias byte
Si se admite esta conversión y, si es así, no se especifica cómo se realiza.
Propuesta:
Permitir conversiones implícitas de una constante de string
con el valor null
a byte[]
, Span<byte>
y ReadOnlySpan<byte>
. El valor del tipo de destino como resultado de la conversión es default
.
Solución:
Se aprueba la propuesta: https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversions-from-null-literals.
(Resuelto) ¿A dónde pertenece string_constant_to_UTF8_byte_representation_conversion?
¿Es string_constant_to_UTF8_byte_representation_conversion un punto en la sección de conversiones implícitas §10.2 por sí mismo, o es parte de §10.2.11, o pertenece a algún otro grupo de conversiones implícitas existente?
Propuesta:
Es un nuevo punto en la sección §10.2 de conversiones implícitas, similar a "Conversiones implícitas de cadenas interpoladas" o "Conversiones de grupos de métodos". No parece que pertenezca a "conversiones de expresiones constantes implícitas" porque, aunque el origen es una expresión constante, el resultado nunca es una expresión constante. Además, las conversiones de expresiones constantes implícitas se consideran "conversiones implícitas estándar" §10.4.2, lo que es probable que lleve a cambios de comportamiento no triviales que implican conversiones definidas por el usuario.
Solución:
Presentaremos un nuevo tipo de conversión para la constante de cadena a bytes UTF-8: https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-kinds
(Resuelto) ¿Es string_constant_to_UTF8_byte_representation_conversion una conversión estándar?
Además de las conversiones estándar "puras" (las conversiones estándar son aquellas conversiones predefinidas que pueden producirse como parte de una conversión definida por el usuario), el compilador también trata algunas conversiones predefinidas como 'algo' estándar. Por ejemplo, una conversión implícita de cadena interpolada puede producirse como parte de una conversión definida por el usuario si hay una conversión explícita al tipo de destino en el código. Como si fuera una conversión explícita estándar, aunque es una conversión implícita no incluida explícitamente en el conjunto de conversiones implícitas o explícitas estándar. Por ejemplo:
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();
}
Propuesta:
La nueva conversión no es una conversión estándar. Esto evitará cambios de comportamiento no triviales que impliquen conversiones definidas por el usuario. Por ejemplo, no tendremos que preocuparnos de las conversiones definidas por el usuario en las conversiones literales implícitas de tuplas, etc.
Solución:
No es una conversión estándar, por ahora: https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#implicit-standard-conversion.
(Resuelto) Conversión de árbol de expresión LINQ
¿Debería permitirse la conversión string_constant_to_UTF8_byte_representation_conversion en el contexto de una conversión de árbol de expresión Linq? Podemos impedirlo por ahora, o simplemente podemos incluir la forma "rebajada" en el árbol. Por ejemplo:
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})
¿Qué ocurre con los literales de cadena con sufijo u8
? Podríamos emerger como creaciones de matrices de bytes:
Expression<Func<byte[]>> x = () => "hello"u8; // () => new [] {104, 101, 108, 108, 111}
Solución:
No permitido en árboles de expresión Linq: https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#expression-tree-representation.
(Resuelto) Tipo natural de un literal de cadena con sufijo u8
La sección "Diseño detallado" dice: "Aunque el tipo natural será ReadOnlySpan<byte>
". Al mismo tiempo: "Cuando se usa el sufijo u8
, el literal todavía se puede convertir en cualquiera de los tipos permitidos: byte[]
, Span<byte>
o ReadOnlySpan<byte>
".
Hay varias desventajas con este enfoque:
ReadOnlySpan<byte>
no está disponible en el entorno de escritorio.- No hay conversiones existentes de
ReadOnlySpan<byte>
abyte[]
oSpan<byte>
. Para poder soportarlos probablemente necesitaremos tratar los literales como de tipo objetivo. Tanto las reglas de lenguaje como la implementación serán más complicadas.
Propuesta:
El tipo natural será byte[]
. Está disponible fácilmente en todos los marcos. BTW, en tiempo de ejecución siempre comenzaremos con la creación de una matriz de bytes, incluso con la propuesta original. Tampoco se necesitan reglas de conversión especiales para admitir conversiones en Span<byte>
y ReadOnlySpan<byte>
. Ya hay conversiones implícitas definidas por el usuario de byte[]
a Span<byte>
y ReadOnlySpan<byte>
. Incluso hay una conversión implícita definida por el usuario en ReadOnlyMemory<byte>
(consulte la pregunta "Profundidad de la conversión" a continuación). Hay una desventaja, el lenguaje no permite encadenar conversiones definidas por el usuario. Por lo tanto, el código siguiente no se compilará:
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();
}
Sin embargo, al igual que con cualquier conversión definida por el usuario, se puede utilizar una conversión explícita para hacer que una conversión definida por el usuario forme parte de otra conversión definida por el
Parece que todos los escenarios motivadores se abordarán con byte[]
como tipo natural, pero las reglas de lenguaje y la implementación serán significativamente más sencillas.
Solución:
Se aprueba la propuesta: https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#natural-type-of-u8-literals.
Probablemente querremos tener un debate más profundo sobre si los literales de cadena u8
deberían tener un tipo de matriz mutable, pero no creemos que ese debate sea necesario por ahora.
Solo se ha implementado el operador de conversión explícito.
(Resuelto) Profundidad de la conversión
¿También funcionará en cualquier lugar en el que un byte[] pueda funcionar? Ten en cuenta:
static readonly ReadOnlyMemory<byte> s_data1 = "Data"u8;
static readonly ReadOnlyMemory<byte> s_data2 = "Data";
Es probable que el primer ejemplo funcione debido al tipo natural que procede de u8
.
El segundo ejemplo es difícil de hacer que funcione porque requiere conversiones en ambas direcciones. A menos que agreguemos ReadOnlyMemory<byte>
como uno de los tipos de conversión permitidos.
Propuesta:
No hagas nada especial.
Solución:
No se han agregado nuevos destinos de conversión por ahora https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#conversion-depth. Ninguna de las dos conversiones compila.
(Resuelto) Interrupciones en la resolución de sobrecargas
La SIGUIENTE API sería ambigua:
M("");
static void M1(ReadOnlySpan<char> charArray) => ...;
static void M1(byte[] byteArray) => ...;
¿Qué debemos hacer para abordar esto?
Propuesta:
Similar a https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md#overload-resolution, se actualiza el miembro de función Better (§11.6.4.3) para preferir miembros en los que ninguna de las conversiones implicadas requiera convertir constantes string
a secuencias UTF8 byte
.
Mejor miembro de función
... Dada una lista de argumentos
A
con un conjunto de expresiones de argumento{E1, E2, ..., En}
y dos miembros de función aplicablesMp
yMq
con tipos de parámetro{P1, P2, ..., Pn}
y{Q1, Q2, ..., Qn}
,Mp
se define como un miembro de función mejor queMq
si
- para cada argumento, la conversión implícita de
Ex
aPx
no es un string_constant_to_UTF8_byte_representation_conversion y, para al menos un argumento, la conversión implícita deEx
aQx
es una string_constant_to_UTF8_byte_representation_conversion o- para cada argumento, la conversión implícita de
Ex
aPx
no es una función de conversión de tipo y
Mp
es un método no genérico oMp
es un método genérico con parámetros de tipo{X1, X2, ..., Xp}
y para cada parámetro de tipoXi
el argumento de tipo se deduce de una expresión o de un tipo distinto de un function_type, y- para al menos un argumento, la conversión implícita de
Ex
aQx
es un function_type_conversion, oMq
es un método genérico con parámetros de tipo{Y1, Y2, ..., Yq}
y para al menos un parámetro de tipoYi
el argumento de tipo se deduce de un function_type, o- para cada argumento, la conversión implícita de
Ex
aQx
no es mejor que la conversión implícita deEx
aPx
y, para al menos un argumento, la conversión deEx
aPx
es mejor que la conversión deEx
aQx
.
Tenga en cuenta que la incorporación de esta regla no cubrirá escenarios en los que los métodos de instancia se vuelven aplicables y "ocultan" métodos de extensión. Por ejemplo:
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";
}
El comportamiento de este código cambiará silenciosamente de imprimir "string" a imprimir "byte[]".
¿Estamos bien con este cambio de comportamiento? ¿Debe documentarse como un cambio importante?
Tenga en cuenta que no hay ninguna propuesta para hacer que string_constant_to_UTF8_byte_representation_conversion no esté disponible cuando la versión del lenguaje C#10 sea el objetivo. En ese caso, el ejemplo anterior se convierte en un error en lugar de volver al comportamiento de C#10. Esto sigue un principio general que la versión del lenguaje de destino no afecta a la semántica del lenguaje.
¿Estamos bien con este comportamiento? ¿Debe documentarse como un cambio importante?
La nueva regla tampoco va a evitar roturas que impliquen conversiones literales de tupla. Por ejemplo
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";
}
va a imprimir silenciosamente "matriz" en lugar de "objeto".
¿Estamos bien con este comportamiento? ¿Debe documentarse como un cambio importante? Quizás podríamos complicar la nueva regla para indagar en las conversiones de literales de tupla.
Solución:
El prototipo no ajustará ninguna regla aquí, por lo que es de esperar que podamos ver lo que se rompe en la práctica - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#breaking-changes.
(Resuelto) ¿Debería el sufijo u8
no distinguir entre mayúsculas y minúsculas?
Propuesta:
Admite también el sufijo U8
para la coherencia con los sufijos numéricos.
Solución:
Aprobado - https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-26.md#suffix-case-sensitivity.
Ejemplos de hoy
Ejemplos de dónde runtime ha codificado manualmente los bytes UTF8 hoy
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/Common/src/System/Net/Http/aspnetcore/Http2/Hpack/StatusCodes.cs#L13-L78
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Memory/src/System/Buffers/Text/Base64Encoder.cs#L581-L591
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpResponseStream.Windows.cs#L284
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs#L30
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs#L852
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Text.Json/src/System/Text/Json/JsonConstants.cs#L35-L42
Ejemplos en los que dejamos perf sobre la mesa
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Security/src/System/Net/Security/Pal.Managed/SafeChannelBindingHandle.cs#L16-L17
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L37-L43
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs#L78
- https://github.com/dotnet/runtime/blob/e095fde94baa480a6d65dfdee43d9cc0ad0d0b38/src/libraries/System.Net.Mail/src/System/Net/Mail/SmtpCommands.cs#L669-L687
Reuniones de diseño
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
C# feature specifications