Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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 y 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 idioma (LDM) pertinentes.
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.
Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/8714
Resumen
Presentamos compatibilidad de primera clase con Span<T> y ReadOnlySpan<T> en el lenguaje, incluidos los nuevos tipos de conversión implícitos y los consideramos en más lugares, lo que permite una programación más natural con estos tipos enteros.
Motivación
Desde su introducción en C# 7.2, Span<T> y ReadOnlySpan<T> han trabajado su forma en la biblioteca de clases base y de lenguaje (BCL) de muchas maneras clave. Esto es ideal para los desarrolladores, ya que su introducción mejora el rendimiento sin costar la seguridad del desarrollador. Sin embargo, el lenguaje ha mantenido estos tipos a la longitud de arm de algunas maneras clave, lo que dificulta expresar la intención de las API y conduce a una cantidad significativa de duplicación de área expuesta para las nuevas API. Por ejemplo, la BCL ha agregado una serie de nuevas API primitivas de tensor en .NET 9, pero estas API se ofrecen en ReadOnlySpan<T>. C# no reconoce la relación entre ReadOnlySpan<T>, Span<T>y T[], por lo que aunque haya conversiones definidas por el usuario entre estos tipos, no se pueden usar para receptores de métodos de extensión, no se pueden componer con otras conversiones definidas por el usuario y no ayudan con todos los escenarios de inferencia de tipos genéricos.
Los usuarios necesitarían usar conversiones explícitas o argumentos de tipo, lo que significa que las herramientas del IDE no guían a los usuarios para usar estas API, ya que nada indicará al IDE que es válido para pasar estos tipos después de la conversión. Para proporcionar la máxima facilidad de uso para este estilo de API, la BCL tendrá que definir un conjunto completo de Span<T> sobrecargas y T[] , que es una gran cantidad de área expuesta duplicada para mantener sin ninguna ganancia real. Esta propuesta busca abordar el problema haciendo que el lenguaje reconozca más directamente estos tipos y conversiones.
Por ejemplo, la BCL solo puede agregar una sobrecarga de cualquier MemoryExtensions asistente como:
int[] arr = [1, 2, 3];
Console.WriteLine(
arr.StartsWith(1) // CS8773 in C# 13, permitted with this proposal
);
public static class MemoryExtensions
{
public static bool StartsWith<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T> => span.Length != 0 && EqualityComparer<T>.Default.Equals(span[0], value);
}
Anteriormente, las sobrecargas span y array serían necesarias para hacer que el método de extensión se pueda usar en variables con tipo span/array porque las conversiones definidas por el usuario (que existen entre Span/array/ReadOnlySpan) no se tienen en cuenta para receptores de extensiones.
Diseño detallado
Los cambios de esta propuesta estarán vinculados a LangVersion >= 14.
Conversiones de intervalo
Agregamos un nuevo tipo de conversión implícita a la lista en §10.2.1, una conversión de intervalo implícita. Esta conversión es una conversión de tipo y se define de la siguiente manera:
Una conversión de intervalo implícita permite array_typesconvertir , System.Span<T>System.ReadOnlySpan<T>, y string entre sí de la siguiente manera:
- Desde cualquier unidimensional
array_typecon tipoEide elemento aSystem.Span<Ei> - De cualquier unidimensional
array_typecon tipoEide elemento aSystem.ReadOnlySpan<Ui>, siempre queEisea covarianza-convertible (§18.2.3.3) aUi - De
System.Span<Ti>aSystem.ReadOnlySpan<Ui>, siempre queTisea covarianza-convertible (§18.2.3.3) aUi - De
System.ReadOnlySpan<Ti>aSystem.ReadOnlySpan<Ui>, siempre queTisea covarianza-convertible (§18.2.3.3) aUi - De
stringaSystem.ReadOnlySpan<char>
Los tipos Span/ReadOnlySpan se consideran aplicables a la conversión si son ref structy coinciden con su nombre completo (LDM 2024-06-24).
También agregamos la conversión de intervalo implícita a la lista de conversiones implícitas estándar (§10.4.2). Esto permite que la resolución de sobrecarga las tenga en cuenta al realizar la resolución de argumentos, como en la propuesta de API vinculada anteriormente.
Las conversiones de intervalo explícitas son las siguientes:
- Todas las conversiones implícitas de intervalo.
- Desde un
No hay ninguna conversión de intervalo explícita estándar a diferencia de otras conversiones explícitas estándar (§10.4.3) que siempre existen dada la conversión implícita estándar opuesta.
Conversiones definidas por el usuario
Las conversiones definidas por el usuario no se consideran al convertir entre tipos para los que existe una conversión implícita o explícita de intervalo.
Las conversiones de intervalo implícitas están exentas de la regla que no es posible definir un operador definido por el usuario entre tipos para los que existe una conversión no definida por el usuario (§10.5.2 Conversiones definidas por el usuario permitidas). Esto es necesario para que la BCL pueda seguir definiendo los operadores de conversión de intervalo existentes incluso cuando cambian a C# 14 (siguen siendo necesarios para langVersions inferiores y también porque estos operadores se usan en codegen de las nuevas conversiones de intervalo estándar). Pero se puede ver como un detalle de implementación (codegen y la versión inferior de LangVersions no forman parte de la especificación) y Roslyn infringe esta parte de la especificación de todos modos (esta regla en particular sobre las conversiones definidas por el usuario no se aplica).
Receptor de extensión
También agregamos la conversión de intervalo implícita a la lista de conversiones implícitas aceptables en el primer parámetro de un método de extensión al determinar la aplicabilidad (12.8.9.3) (cambio en negrita):
Un método de extensión
Cᵢ.Mₑes elegible si:
Cᵢes una clase no genérica y no anidada- El nombre de
Mₑes identificadorMₑes accesible y aplicable cuando se aplique a los argumentos como un método estático como se muestra arriba- Existe una identidad implícita, referencia o conversión boxing
, boxing o span desde expr hasta el tipo del primer parámetro deMₑ. La conversión de intervalo no se considera cuando se realiza la resolución de sobrecarga para una conversión de grupo de métodos.
Tenga en cuenta que la conversión implícita de intervalo no se considera para el receptor de extensiones en conversiones de grupo de métodos (LDM 2024-07-15), lo que hace que el código siguiente siga funcionando en lugar de producir un error CS1113: Extension method 'E.M<int>(Span<int>, int)' defined on value type 'Span<int>' cannot be used to create delegatesen tiempo de compilación:
using System;
using System.Collections.Generic;
Action<int> a = new int[0].M; // binds to M<int>(IEnumerable<int>, int)
static class E
{
public static void M<T>(this Span<T> s, T x) => Console.Write(1);
public static void M<T>(this IEnumerable<T> e, T x) => Console.Write(2);
}
Como posible trabajo futuro, podríamos considerar la posibilidad de quitar esta condición que la conversión de intervalo no se considera para el receptor de extensiones en conversiones de grupo de métodos y, en su lugar, implementar cambios para que el escenario como el anterior termine llamando correctamente a la Span sobrecarga en su lugar:
- El compilador podría emitir un thunk que tomaría la matriz como receptor y realizar la conversión de intervalo dentro (de forma similar al usuario que crea manualmente el delegado como
x => new int[0].M(x)). - Los delegados de valor si se implementan podrían tomar como
Spanreceptor directamente.
Desviación
El objetivo de la sección de varianza en la conversión de intervalo implícito es replicar cierta cantidad de covarianza para System.ReadOnlySpan<T>. Los cambios en tiempo de ejecución serían necesarios para implementar completamente la varianza a través de genéricos aquí (consulte .. /csharp-13.0/ref-struct-interfaces.md para usar ref struct tipos en genéricos), pero podemos permitir una cantidad limitada de covarianza mediante el uso de una API de .NET 9 propuesta: https://github.com/dotnet/runtime/issues/96952. Esto permitirá que el idioma trate System.ReadOnlySpan<T> como si se T declarase como out T en algunos escenarios. Sin embargo, no aplicamos esta conversión de variante a través de todos los escenarios de varianza y no la agregamos a la definición de varianza convertible en §18.2.3.3. Si en el futuro, cambiamos el tiempo de ejecución para comprender más profundamente la varianza aquí, podemos tomar el cambio importante menor para reconocerlo completamente en el lenguaje.
Patrones
Tenga en cuenta que cuando ref structse usan como un tipo en cualquier patrón, solo se permiten conversiones de identidad:
class C<T> where T : allows ref struct
{
void M1(T t) { if (t is T x) { } } // ok (T is T)
void M2(R r) { if (r is R x) { } } // ok (R is R)
void M3(T t) { if (t is R x) { } } // error (T is R)
void M4(R r) { if (r is T x) { } } // error (R is T)
}
ref struct R { }
A partir de la especificación del operador is-type (§12.12.12.12.1):
El resultado de la operación
E is T[...] es un valor booleano que indica siEno es null y se puede convertir correctamente al tipoTmediante una conversión de referencia, una conversión de conversión boxing, una conversión unboxing, una conversión de ajuste o una conversión de desencapsulado.[...]
Si
Tes un tipo de valor no anulable, el resultado estruesiDyTson del mismo tipo.
Este comportamiento no cambia con esta característica, por lo que no será posible escribir patrones para Span/ReadOnlySpan, aunque son posibles patrones similares para matrices (incluida la varianza):
using System;
M1<object[]>(["0"]); // prints
M1<string[]>(["1"]); // prints
void M1<T>(T t)
{
if (t is object[] r) Console.WriteLine(r[0]); // ok
}
void M2<T>(T t) where T : allows ref struct
{
if (t is ReadOnlySpan<object> r) Console.WriteLine(r[0]); // error
}
Generación de código
Las conversiones siempre existirán, independientemente de si hay asistentes en tiempo de ejecución que se usen para implementarlas (LDM 2024-05-13). Si los asistentes no están presentes, si intenta usar la conversión, se producirá un error en tiempo de compilación que falta un miembro necesario para el compilador.
El compilador espera usar los siguientes asistentes o equivalentes para implementar las conversiones:
| Conversión | Helpers |
|---|---|
| matriz en Span |
static implicit operator Span<T>(T[]) (definido en Span<T>) |
| matriz en ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(T[]) (definido en ReadOnlySpan<T>) |
| Intervalo a ReadOnlySpan |
static implicit operator ReadOnlySpan<T>(Span<T>) (definido en Span<T>) y static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| ReadOnlySpan a ReadOnlySpan | static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>) |
| string a ReadOnlySpan | static ReadOnlySpan<char> MemoryExtensions.AsSpan(string) |
Tenga en cuenta que MemoryExtensions.AsSpan se usa en lugar del operador implícito equivalente definido en string.
Esto significa que el codegen es diferente entre LangVersions (el operador implícito se usa en C# 13; el método AsSpan estático se usa en C# 14).
Por otro lado, la conversión se puede emitir en .NET Framework (el AsSpan método existe allí mientras que el string operador no).
La conversión explícita a (ReadOnly)Span se convierte explícitamente de la matriz de origen en una matriz con el tipo de elemento de destino y, a continuación, a (ReadOnly)Span a través del mismo asistente que una conversión implícita usaría, es decir, el correspondiente op_Implicit(T[]).
Mejor conversión a partir de expresión
Se actualiza una mejor conversión de la expresión (§12.6.4.5) para preferir conversiones de intervalo implícitas. Esto se basa en los cambios de resolución de sobrecarga de expresiones de colección.
Dada una conversión implícita
C₁que convierte de una expresiónEa un tipoT₁, y una conversión implícitaC₂que convierte de una expresiónEa un tipoT₂,C₁es una conversión mejor queC₂si se cumple una de las siguientes condiciones:
Ees una expresión de colección y es unaC₁queC₂Eno es una expresión de colección y se cumple una de las siguientes condiciones:
EcoincidenciasT₁exactas yEno coincide exactamenteT₂Ecoincide exactamente con ninguna deT₁y , yT₂es una conversión de intervalo implícita yC₁no es unaC₂conversión de intervalo implícitaEcoincide exactamente con o ninguno de y , o ambos o ninguno de yT₁son una conversión deT₂intervalo implícita, yC₁es unC₂de conversión mejor queT₁T₂Ees un grupo de métodos,T₁es compatible con el mejor método del grupo de métodos para la conversión yC₁no es compatible con el único método del grupo de métodos para la conversiónT₂.C₂
Mejor destino de conversión
Dados dos tipos
T₁yT₂,T₁es un mejor destino de conversión queT₂si se cumple una de las siguientes condiciones:
T₁esSystem.ReadOnlySpan<E₁>,T₂esSystem.Span<E₂>y existe una conversión de identidad deE₁aE₂.T₁es ,System.ReadOnlySpan<E₁>esT₂System.ReadOnlySpan<E₂>y existe una conversión implícita deT₁aT₂y no existe ninguna conversión implícita deT₂aT₁.- Al menos uno de
T₁oT₂noSystem.ReadOnlySpan<Eᵢ>es y noSystem.Span<Eᵢ>es , y existe una conversión implícita deT₁aT₂y no existe ninguna conversión implícita deT₂aT₁.- ...
Diseñar reuniones:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#preferring-readonlyspant-over-spant-conversions
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-09.md#first-class-span-open-questions
Comentarios de mejora
La mejor conversión de la regla de expresión debe asegurarse de que cada vez que se aplique una sobrecarga debido a las nuevas conversiones de intervalo, se evita cualquier ambigüedad potencial con otra sobrecarga porque se prefiere la sobrecarga recién aplicable.
Sin esta regla, el código siguiente que se compiló correctamente en C# 13 provocaría un error de ambigüedad en C# 14 debido a la nueva conversión implícita estándar de la matriz a ReadOnlySpan aplicable a un receptor de métodos de extensión:
using System;
using System.Collections.Generic;
var a = new int[] { 1, 2, 3 };
a.M();
static class E
{
public static void M(this IEnumerable<int> x) { }
public static void M(this ReadOnlySpan<int> x) { }
}
La regla también permite introducir nuevas API que generarían ambigüedades anteriormente, por ejemplo:
using System;
using System.Collections.Generic;
C.M(new int[] { 1, 2, 3 }); // would be ambiguous before
static class C
{
public static void M(IEnumerable<int> x) { }
public static void M(ReadOnlySpan<int> x) { } // can be added now
}
Advertencia
Dado que la regla de mejora se define para las conversiones de intervalo que solo existen en LangVersion >= 14, los autores de API no pueden agregar estas nuevas sobrecargas si quieren seguir admitiendo a los usuarios en LangVersion <= 13.
Por ejemplo, si la BCL de .NET 9 presenta estas sobrecargas, los usuarios que actualizan a net9.0 TFM pero permanecen en la versión inferior de LangVersion obtendrán errores de ambigüedad para el código existente.
Consulte también una pregunta abierta a continuación.
Inferencia de tipo
Actualizamos la sección de inferencias de tipo de la especificación como se indica a continuación (cambios en negrita).
12.6.3.9 Inferencias exactas
La inferencia exactade un tipo
Ua otro tipoVse realiza del siguiente modo:
- Si
Ves uno de losXᵢno fijos, entoncesUse añade al conjunto de límites exactos paraXᵢ.- En caso contrario, los conjuntos
V₁...VₑyU₁...Uₑse determinan comprobando si se da alguno de los siguientes casos:
Ves un tipo de matrizV₁[...]yUes un tipo de matrizU₁[...]del mismo rangoVes ySpan<V₁>es unUtipoU₁[]de matriz o unSpan<U₁>Ves yReadOnlySpan<V₁>Ues un tipoU₁[]de matriz o oSpan<U₁>ReadOnlySpan<U₁>Ves el tipoV₁?yUes el tipoU₁Ves un tipo construidoC<V₁...Vₑ>yUes un tipo construidoC<U₁...Uₑ>
Si se da alguno de estos casos, se hace una inferencia exacta de cada uno deUᵢal correspondienteVᵢ.- En caso contrario, no se realizan inferencias.
12.6.3.10 Inferencias de límite inferior
Una inferencia de límite inferior de un tipo
Ua otro tipoVse hace como sigue:
- Si
Ves uno de losXᵢno fijados, entonces se agregaUal conjunto de límites inferiores paraXᵢ.- De lo contrario, si
Ves el tipoV₁?yUes el tipoU₁?entonces se hace una inferencia de límite inferior deU₁aV₁.- En caso contrario, los conjuntos
U₁...UₑyV₁...Vₑse determinan comprobando si se da alguno de los siguientes casos:
Ves un tipoV₁[...]de matriz yUes un tipoU₁[...]de matriz del mismo rango.Ves ySpan<V₁>es unUtipoU₁[]de matriz o unSpan<U₁>Ves yReadOnlySpan<V₁>Ues un tipoU₁[]de matriz o oSpan<U₁>ReadOnlySpan<U₁>Ves uno deIEnumerable<V₁>,ICollection<V₁>,IReadOnlyList<V₁>>,IReadOnlyCollection<V₁>oIList<V₁>yUes un tipo de matriz unidimensionalU₁[]Ves un tipo construidoclass,struct,interfaceodelegateC<V₁...Vₑ>y hay un tipo únicoC<U₁...Uₑ>de modo queU(o, siUes un tipoparameter, su clase base efectiva o cualquier miembro de su conjunto de interfaz eficaz) es idéntica a,inheritsde (directa o indirectamente) o implementa (directa o indirectamente)C<U₁...Uₑ>.- (La restricción de "unicidad" significa que en el caso interfaz
C<T>{} class U: C<X>, C<Y>{}, entonces no se hace ninguna inferencia al inferir deUaC<T>porqueU₁podría serXoY.)
Si se aplica cualquiera de estos casos, se realiza una inferencia de cadaUᵢal correspondienteVᵢcomo se indica a continuación:- Si no se sabe que
Uᵢes un tipo de referencia, se hace una inferencia exacta de .- De lo contrario, si
Ues un tipo de matriz,se realiza una inferencia de límite inferiordepende del tipo deV:
- Si
Ves ,Span<Vᵢ>se realiza una inferencia exacta.- Si
Ves un tipo de matriz o ,ReadOnlySpan<Vᵢ>se realiza una inferencia de límite inferior.- De lo contrario, si
Ues unaSpan<Uᵢ>, entonces la inferencia depende del tipo deV:
- Si
Ves ,Span<Vᵢ>se realiza una inferencia exacta.- Si
Ves ,ReadOnlySpan<Vᵢ>se realiza una inferencia de límite inferior.- De lo contrario, si
Ues unReadOnlySpan<Uᵢ>yVes unReadOnlySpan<Vᵢ>, se hace una inferencia de límite inferior:- En caso contrario, si
VesC<V₁...Vₑ>entonces la inferencia depende del parámetro de tipoi-thdeC:
- Si es covariante, se realiza una inferencia de límite inferior.
- Si es contravariante, se realiza una inferencia de límite superior.
- Si es invariable, se realiza una inferencia exacta de .
- En caso contrario, no se realizan inferencias.
No hay reglas para la inferencia de límite superior porque no sería posible alcanzarlas.
La inferencia de tipos nunca se inicia como límite superior, tendría que pasar por una inferencia de límite inferior y un parámetro de tipo contravariante.
Debido a la regla "si Uᵢ no se sabe que es un tipo de referencia, se realiza una inferencia exacta ", el argumento de tipo de origen no pudo ser Span/ReadOnlySpan (esos no pueden ser tipos de referencia).
Sin embargo, la inferencia de intervalo de límite superior solo se aplicaría si el tipo de origen fuera un Span/ReadOnlySpan, ya que tendría reglas como:
Ues ySpan<U₁>es unVtipoV₁[]de matriz o unSpan<V₁>Ues yReadOnlySpan<U₁>Ves un tipoV₁[]de matriz o oSpan<V₁>ReadOnlySpan<V₁>
Cambios críticos
Como cualquier propuesta que cambie las conversiones de escenarios existentes, esta propuesta introduce algunos nuevos cambios importantes. Estos son algunos ejemplos:
Llamar a Reverse en una matriz
Al llamar x.Reverse() a donde x es una instancia de tipo T[] se enlazaría anteriormente a IEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>), mientras que ahora se enlaza a void MemoryExtensions.Reverse<T>(this Span<T>).
Desafortunadamente, estas API son incompatibles (esta última realiza la inversión en contexto y devuelve void).
.NET 10 mitiga esto agregando una sobrecarga IEnumerable<T> Reverse<T>(this T[])específica de la matriz , vea https://github.com/dotnet/runtime/issues/107723.
void M(int[] a)
{
foreach (var x in a.Reverse()) { } // fine previously, an error now (`Reverse` returns `void`)
foreach (var x in Enumerable.Reverse(a)) { } // workaround
}
Consulte también:
- https://developercommunity.visualstudio.com/t/Extension-method-SystemLinqEnumerable/10790323
- https://developercommunity.visualstudio.com/t/Compilation-Error-When-Calling-Reverse/10818048
- https://developercommunity.visualstudio.com/t/Version-17131-has-an-obvious-defect-th/10858254
- https://developercommunity.visualstudio.com/t/Visual-Studio-2022-update-breaks-build-w/10856758
- https://github.com/dotnet/runtime/issues/111532
- https://developercommunity.visualstudio.com/t/Backward-compatibility-issue-:-IEnumerab/10896189#T-ND10896782
Reunión de diseño: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse
Ambigüedades
En los ejemplos siguientes se produjo un error en la inferencia de tipos anteriores para la sobrecarga span, pero ahora la inferencia de tipos de matriz a Span se realiza correctamente, por lo que estas son ambiguas.
Para solucionar este problema, los usuarios pueden usar .AsSpan() o los autores de API pueden usar OverloadResolutionPriorityAttribute.
var x = new long[] { 1 };
Assert.Equal([2], x); // previously Assert.Equal<T>(T[], T[]), now ambiguous with Assert.Equal<T>(ReadOnlySpan<T>, Span<T>)
Assert.Equal([2], x.AsSpan()); // workaround
var x = new int[] { 1, 2 };
var s = new ArraySegment<int>(x, 1, 1);
Assert.Equal(x, s); // previously Assert.Equal<T>(T, T), now ambiguous with Assert.Equal<T>(Span<T>, Span<T>)
Assert.Equal(x.AsSpan(), s); // workaround
xUnit agrega más sobrecargas para mitigar esto: https://github.com/xunit/xunit/discussions/3021.
Reunión de diseño: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities
Matrices de Covariante
Las sobrecargas que se toman IEnumerable<T> en matrices covariantes, pero las sobrecargas que toman Span<T> (que ahora preferimos) no, porque la conversión de intervalo produce una ArrayTypeMismatchException para matrices covariantes.
Podría decirse que la Span<T> sobrecarga no debería existir, debería tardar ReadOnlySpan<T> en su lugar.
Para solucionar esto, los usuarios pueden usar o los autores de API pueden usar .AsEnumerable()OverloadResolutionPriorityAttribute o agregar ReadOnlySpan<T> sobrecarga que se prefiere debido a la regla de mejora.
string[] s = new[] { "a" };
object[] o = s;
C.R(o); // wrote 1 previously, now crashes in Span<T> constructor with ArrayTypeMismatchException
C.R(o.AsEnumerable()); // workaround
static class C
{
public static void R<T>(IEnumerable<T> e) => Console.Write(1);
public static void R<T>(Span<T> s) => Console.Write(2);
// another workaround:
public static void R<T>(ReadOnlySpan<T> s) => Console.Write(3);
}
Reunión de diseño: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays
Preferir ReadOnlySpan a través del intervalo
La regla de mejora hace que las preferencias de las sobrecargas readOnlySpan sobre las sobrecargas span eviten ArrayTypeMismatchExceptionlos escenarios de matriz covariante.
Esto puede provocar interrupciones de compilación en algunos escenarios, por ejemplo, cuando las sobrecargas difieren según su tipo de valor devuelto:
double[] x = new double[0];
Span<ulong> y = MemoryMarshal.Cast<double, ulong>(x); // previously worked, now a compilation error (returns ReadOnlySpan, not Span)
Span<ulong> z = MemoryMarshal.Cast<double, ulong>(x.AsSpan()); // workaround
static class MemoryMarshal
{
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span) => default;
public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span) => default;
}
Consulte https://github.com/dotnet/roslyn/issues/76443.
Árboles de expresión
Las sobrecargas que toman intervalos como MemoryExtensions.Contains se prefieren sobre las sobrecargas clásicas, como Enumerable.Contains, incluso dentro de árboles de expresión, pero las estructuras ref no son compatibles con el motor del intérprete:
Expression<Func<int[], int, bool>> exp = (array, num) => array.Contains(num);
exp.Compile(preferInterpretation: true); // fails at runtime in C# 14
Expression<Func<int[], int, bool>> exp2 = (array, num) => Enumerable.Contains(array, num); // workaround
exp2.Compile(preferInterpretation: true); // ok
De forma similar, los motores de traducción como LINQ-to-SQL deben reaccionar a esto si los visitantes de árbol esperan Enumerable.Contains porque se encontrarán MemoryExtensions.Contains en su lugar.
Consulte también:
- https://github.com/dotnet/runtime/issues/109757
- https://github.com/dotnet/docs/issues/43952
- https://github.com/dotnet/efcore/issues/35100
- https://github.com/dotnet/csharplang/discussions/8959
Diseñar reuniones:
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-12-04.md#conversions-in-expression-trees
- https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-01-06.md#ignoring-ref-structs-in-expressions
Conversiones definidas por el usuario mediante herencia
Al agregar conversiones implícitas de intervalo a la lista de conversiones implícitas estándar, podemos cambiar el comportamiento cuando las conversiones definidas por el usuario participan en una jerarquía de tipos. En este ejemplo se muestra ese cambio, en comparación con un escenario entero que ya se comporta como lo hará el nuevo comportamiento de C# 14.
Span<string> span = [];
var d = new Derived();
d.M(span); // Base today, Derived tomorrow
int i = 1;
d.M(i); // Derived today, demonstrates new behavior
class Base
{
public void M(Span<string> s)
{
Console.WriteLine("Base");
}
public void M(int i)
{
Console.WriteLine("Base");
}
}
class Derived : Base
{
public static implicit operator Derived(ReadOnlySpan<string> r) => new Derived();
public static implicit operator Derived(long l) => new Derived();
public void M(Derived s)
{
Console.WriteLine("Derived");
}
}
Consulte también: https://github.com/dotnet/roslyn/issues/78314
Búsqueda de métodos de extensión
Al permitir conversiones implícitas de intervalo en la búsqueda de métodos de extensión, podemos cambiar potencialmente qué método de extensión se resuelve mediante la resolución de sobrecarga.
namespace N1
{
using N2;
public class C
{
public static void M()
{
Span<string> span = new string[0];
span.Test(); // Prints N2 today, N1 tomorrow
}
}
public static class N1Ext
{
public static void Test(this ReadOnlySpan<string> span)
{
Console.WriteLine("N1");
}
}
}
namespace N2
{
public static class N2Ext
{
public static void Test(this Span<string> span)
{
Console.WriteLine("N2");
}
}
}
Preguntas abiertas
Regla de mejora sin restricciones
¿Deberíamos hacer que la regla de mejora sea incondicional en LangVersion? Esto permitiría a los autores de API agregar nuevas API span en las que existen equivalentes IEnumerable sin interrumpir a los usuarios en langVersions anteriores u otros compiladores o lenguajes (por ejemplo, VB). Sin embargo, esto significaría que los usuarios podrían obtener un comportamiento diferente después de actualizar el conjunto de herramientas (sin cambiar LangVersion o TargetFramework):
- El compilador podría elegir diferentes sobrecargas (técnicamente un cambio importante, pero esperamos que esas sobrecargas tengan un comportamiento equivalente).
- Otros saltos podrían surgir, desconocidos en este momento.
Tenga en cuenta que OverloadResolutionPriorityAttribute no puede resolverlo completamente porque también se omite en langVersions anteriores.
Sin embargo, debe ser posible usarlo para evitar ambigüedades de VB donde se debe reconocer el atributo.
Omitir más conversiones definidas por el usuario
Hemos definido un conjunto de pares de tipo para los que hay conversiones de intervalo implícitas y explícitas definidas por el lenguaje.
Siempre que existe una conversión de intervalo definida por el lenguaje desde T1 a T2, cualquier conversión definida por el usuario de T1 a T2 se omite (independientemente del intervalo y la conversión definida por el usuario sea implícita o explícita).
Tenga en cuenta que esto incluye todas las condiciones, por lo que, por ejemplo, no hay ninguna conversión de intervalo de a Span<object> (hay una conversión de ReadOnlySpan<string> intervalo de Span<T> a pero ReadOnlySpan<U> debe contener que T : U), por lo tanto, una conversión definida por el usuario se consideraría entre esos tipos si existiera (que tendría que ser una conversión especializada como Span<T> porque ReadOnlySpan<string> los operadores de conversión no pueden tener parámetros genéricos).
¿Deberíamos omitir las conversiones definidas por el usuario también entre otras combinaciones de tipos array/Span/ReadOnlySpan/string en los que no existe ninguna conversión de intervalo definida por el idioma correspondiente?
Por ejemplo, si hay una conversión definida por el usuario de ReadOnlySpan<T> a Span<T>, ¿deberíamos omitirla?
Posibilidades de especificación que se deben tener en cuenta:
-
Siempre que exista una conversión de intervalo desde
T1aT2, omita cualquier conversión definida por el usuario deT1aT2o desdeT2aT1. -
Las conversiones definidas por el usuario no se consideran al convertir entre
- cualquier unidimensional
array_typeySystem.Span<T>/System.ReadOnlySpan<T>, - cualquier combinación de
System.Span<T>/System.ReadOnlySpan<T>, -
stringySystem.ReadOnlySpan<char>.
- cualquier unidimensional
- Al igual que antes, pero reemplazando el último punto de viñeta por:
-
stringySystem.Span<char>/System.ReadOnlySpan<char>.
-
- Al igual que antes, pero reemplazando el último punto de viñeta por:
-
stringySystem.Span<T>/System.ReadOnlySpan<T>.
-
Técnicamente, la especificación no permite definir algunas de estas conversiones definidas por el usuario: no es posible definir un operador definido por el usuario entre tipos para los que existe una conversión no definida por el usuario (§10.5.2).
Pero Roslyn infringe intencionadamente esta parte de la especificación. Y algunas conversiones como entre Span y string se permiten de todos modos (no existe ninguna conversión definida por el lenguaje entre estos tipos).
Sin embargo, como alternativa , simplemente omitir las conversiones, podríamos impedir que se definan en absoluto y quizás se desconecte la infracción de especificación al menos para estas nuevas conversiones de intervalo, es decir, cambiar Roslyn para notificar realmente un error en tiempo de compilación si estas conversiones están definidas (probablemente excepto las ya definidas por la BCL).
Alternativas
Mantén las cosas como están.
C# feature specifications