Compartir vía


Tipos de intervalo de primera clase

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_type con tipo Ei de elemento a System.Span<Ei>
  • De cualquier unidimensional array_type con tipo Ei de elemento a System.ReadOnlySpan<Ui>, siempre que Ei sea covarianza-convertible (§18.2.3.3) a Ui
  • De System.Span<Ti> a System.ReadOnlySpan<Ui>, siempre que Ti sea covarianza-convertible (§18.2.3.3) a Ui
  • De System.ReadOnlySpan<Ti> a System.ReadOnlySpan<Ui>, siempre que Ti sea covarianza-convertible (§18.2.3.3) a Ui
  • De string a System.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 identificador
  • Mₑ 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 de Mₑ. 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 Span receptor 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 si E no es null y se puede convertir correctamente al tipo T mediante 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 T es un tipo de valor no anulable, el resultado es true si D y T son 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ón E a un tipo T₁, y una conversión implícita C₂ que convierte de una expresión E a un tipo T₂, C₁ es una conversión mejor que C₂ si se cumple una de las siguientes condiciones:

  • Ees una expresión de colección y es una C₁ queC₂
  • E no es una expresión de colección y se cumple una de las siguientes condiciones:
    • E coincidencias T₁ exactas y E no coincide exactamente T₂
    • Ecoincide exactamente con ninguna de T₁ y , y T₂ es una conversión de intervalo implícita y C₁ no es una C₂conversión de intervalo implícita
    • Ecoincide exactamente con o ninguno de y , o ambos o ninguno de y T₁ son una conversión de T₂ intervalo implícita, y C₁ es un C₂ de conversión mejor que T₁T₂
  • Ees un grupo de métodos, T₁ es compatible con el mejor método del grupo de métodos para la conversión y C₁ 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₁ y T₂, T₁ es un mejor destino de conversión que T₂ si se cumple una de las siguientes condiciones:

  • T₁ es System.ReadOnlySpan<E₁>, T₂ es System.Span<E₂>y existe una conversión de identidad de E₁ a E₂ .
  • T₁es , System.ReadOnlySpan<E₁> es T₂System.ReadOnlySpan<E₂>y existe una conversión implícita de T₁ a T₂ y no existe ninguna conversión implícita de T₂ a T₁ .
  • Al menos uno de T₁ o T₂ no System.ReadOnlySpan<Eᵢ> es y no System.Span<Eᵢ>es , y existe una conversión implícita de T₁ a T₂ y no existe ninguna conversión implícita de T₂ a T₁ .
  • ...

Diseñar reuniones:

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 tipo V se realiza del siguiente modo:

  • Si V es uno de los Xᵢ no fijos, entonces U se añade al conjunto de límites exactos para Xᵢ.
  • En caso contrario, los conjuntos V₁...Vₑ y U₁...Uₑ se determinan comprobando si se da alguno de los siguientes casos:
    • V es un tipo de matriz V₁[...] y U es un tipo de matriz U₁[...] del mismo rango
    • Ves y Span<V₁> es un U tipo U₁[] de matriz o unSpan<U₁>
    • Ves y ReadOnlySpan<V₁>U es un tipo U₁[] de matriz o o Span<U₁>ReadOnlySpan<U₁>
    • V es el tipo V₁? y U es el tipo U₁
    • V es un tipo construido C<V₁...Vₑ> y U es un tipo construido C<U₁...Uₑ>
      Si se da alguno de estos casos, se hace una inferencia exacta de cada uno de Uᵢ al correspondiente Vᵢ.
  • 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 tipo V se hace como sigue:

  • Si V es uno de los Xᵢ no fijados, entonces se agrega U al conjunto de límites inferiores para Xᵢ.
  • De lo contrario, si V es el tipo V₁? y U es el tipo U₁? entonces se hace una inferencia de límite inferior de U₁ a V₁.
  • En caso contrario, los conjuntos U₁...Uₑ y V₁...Vₑ se determinan comprobando si se da alguno de los siguientes casos:
    • V es un tipo V₁[...] de matriz y U es un tipo U₁[...]de matriz del mismo rango.
    • Ves y Span<V₁> es un U tipo U₁[] de matriz o unSpan<U₁>
    • Ves y ReadOnlySpan<V₁>U es un tipo U₁[] de matriz o o Span<U₁>ReadOnlySpan<U₁>
    • V es uno de IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> o IList<V₁> y U es un tipo de matriz unidimensional U₁[]
    • V es un tipo construido class, struct, interface o delegateC<V₁...Vₑ> y hay un tipo único C<U₁...Uₑ> de modo que U (o, si U es un tipo parameter, su clase base efectiva o cualquier miembro de su conjunto de interfaz eficaz) es idéntica a, inherits de (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 de U a C<T> porque U₁ podría ser X o Y.)
      Si se aplica cualquiera de estos casos, se realiza una inferencia de cada Uᵢ al correspondiente Vᵢ 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 U es un tipo de matriz, se realiza una inferencia de límite inferiordepende del tipo de V:
      • Si V es , Span<Vᵢ>se realiza una inferencia exacta.
      • Si V es un tipo de matriz o , ReadOnlySpan<Vᵢ>se realiza una inferencia de límite inferior.
    • De lo contrario, si U es una Span<Uᵢ>, entonces la inferencia depende del tipo de V:
      • Si V es , Span<Vᵢ>se realiza una inferencia exacta.
      • Si V es , ReadOnlySpan<Vᵢ>se realiza una inferencia de límite inferior.
    • De lo contrario, si U es un ReadOnlySpan<Uᵢ> y V es un ReadOnlySpan<Vᵢ>, se hace una inferencia de límite inferior:
    • En caso contrario, si V es C<V₁...Vₑ> entonces la inferencia depende del parámetro de tipo i-th de C:
      • 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 y Span<U₁> es un V tipo V₁[] de matriz o unSpan<V₁>
  • Ues y ReadOnlySpan<U₁>V es un tipo V₁[] de matriz o o Span<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:

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:

Diseñar reuniones:

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:

  1. Siempre que exista una conversión de intervalo desde T1 a T2, omita cualquier conversión definida por el usuario de T1 a T2o desde T2 a T1.

  2. Las conversiones definidas por el usuario no se consideran al convertir entre

    • cualquier unidimensional array_type y System.Span<T>/System.ReadOnlySpan<T>,
    • cualquier combinación de System.Span<T>/System.ReadOnlySpan<T>,
    • string y System.ReadOnlySpan<char>.
  3. Al igual que antes, pero reemplazando el último punto de viñeta por:
    • string y System.Span<char>/System.ReadOnlySpan<char>.
  4. Al igual que antes, pero reemplazando el último punto de viñeta por:
    • string y System.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.