Compartilhar via


Tipos de span de primeira classe

Observação

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

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

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.

Problema do especialista: https://github.com/dotnet/csharplang/issues/8714

Resumo

Introduzimos suporte de primeira classe para Span<T> e ReadOnlySpan<T> na linguagem, incluindo novos tipos de conversão implícitos e os consideramos em mais lugares, permitindo uma programação mais natural com esses tipos integrais.

Motivação

Desde sua introdução no C# 7.2, Span<T> e ReadOnlySpan<T> trabalharam seu caminho para a linguagem e a BCL (biblioteca de classes base) de várias maneiras importantes. Isso é ótimo para desenvolvedores, pois sua introdução melhora o desempenho sem custar a segurança do desenvolvedor. No entanto, a linguagem manteve esses tipos à distância de um braço de algumas formas cruciais, o que torna difícil expressar a intenção das APIs e leva a uma quantidade significativa de duplicação de área de superfície para novas APIs. Por exemplo, a BCL adicionou várias novas APIs primitivas de tensor no .NET 9, mas todas essas APIs são oferecidas no ReadOnlySpan<T>. O C# não reconhece a relação entre ReadOnlySpan<T>, Span<T>e , T[]portanto, embora haja conversões definidas pelo usuário entre esses tipos, elas não podem ser usadas para receptores de método de extensão, não podem ser compostas com outras conversões definidas pelo usuário e não ajudam em todos os cenários de inferência de tipo genérico. Os usuários precisariam usar conversões explícitas ou argumentos de tipo, o que significa que as ferramentas do IDE não estão orientando os usuários a usar essas APIs, pois nada indicará ao IDE que é válido passar esses tipos após a conversão. Para oferecer o máximo de usabilidade para esse estilo de API, a BCL terá que definir um conjunto inteiro de sobrecargas Span<T> e T[], o que representa uma grande área de superfície duplicada a ser mantida sem nenhum ganho real. Esta proposta procura resolver o problema fazendo com que a linguagem reconheça mais diretamente esses tipos e conversões.

Por exemplo, o BCL pode adicionar apenas uma sobrecarga de qualquer assistente de MemoryExtensions, 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, as sobrecargas de Span e array seriam necessárias para tornar o método de extensão utilizável em variáveis do tipo Span/array porque as conversões definidas pelo usuário (que existem entre Span/array/ReadOnlySpan) não são consideradas para receptores de extensão.

Design detalhado

As alterações introduzidas na presente proposta serão associadas a LangVersion >= 14.

Conversões de span

Adicionamos um novo tipo de conversão implícita à lista em §10.2.1, uma conversão de implícita. Essa conversão é uma conversão de tipo e é definida da seguinte maneira:


Uma conversão implícita de array_typesspan permite que , System.Span<T>, System.ReadOnlySpan<T>, e string sejam convertidos entre si da seguinte maneira:

  • De qualquer array_type unidimensional com o tipo de elemento Ei a System.Span<Ei>
  • De qualquer array_type unidimensional com tipo de elemento Ei para System.ReadOnlySpan<Ui>, desde que Ei seja conversível em covariância (§18.2.3.3) para Ui.
  • De System.Span<Ti> para System.ReadOnlySpan<Ui>, desde que Ti seja conversível em covariância (§18.2.3.3) para Ui
  • De System.ReadOnlySpan<Ti> para System.ReadOnlySpan<Ui>, desde que Ti seja conversível em covariância (§18.2.3.3) para Ui
  • De string a System.ReadOnlySpan<char>

Todos os tipos Span/ReadOnlySpan são considerados aplicáveis para a conversão se forem ref struct e corresponderem pelo seu nome completo qualificado (LDM 2024-06-24).

Também adicionamos a conversão implícita de span à lista de conversões implícitas padrão (§10.4.2). Isso permite que a resolução de sobrecarga os considere ao executar a resolução de argumentos, como na proposta de API vinculada anteriormente.

As conversões de span explícitas são as seguintes:

  • Todas as conversões de span implícitas.
  • De um array_type com tipo de elemento Ti para System.Span<Ui> ou System.ReadOnlySpan<Ui> desde que exista uma conversão de referência explícita de Ti para Ui.

Não há nenhuma conversão explícita padrão de span, ao contrário de outras conversões explícitas padrão (§10.4.3), que sempre existem dada a conversão implícita padrão oposta.

Conversões definidas pelo usuário

As conversões definidas pelo usuário não são consideradas ao converter entre tipos para os quais existe uma conversão de intervalo implícita ou explícita.

As conversões de intervalo implícitas são isentas da regra de que não é possível definir um operador definido pelo usuário entre os tipos para os quais existe uma conversão não definida pelo usuário (§10.5.2 Conversões definidas pelo usuário permitidas). Isso é necessário para que o BCL possa continuar definindo os operadores de conversão de Span existentes mesmo quando migrarem para o C# 14 (eles ainda são necessários para versões de linguagem inferiores e também porque esses operadores são usados na geração de código das novas conversões padrão de Span). Mas pode ser visto como um detalhe de implementação (codegen e LangVersions inferiores não fazem parte da especificação) e Roslyn viola essa parte da especificação de qualquer maneira (esta regra específica sobre conversões definidas pelo usuário não é imposta).

Receptor de extensão

Também adicionamos a conversão implícita de span à lista de conversões implícitas aceitáveis no primeiro parâmetro de um método de extensão ao determinar a aplicabilidade (12.8.9.3) (alteração em negrito):

Um método Cᵢ.Mₑ de extensão será qualificado se:

  • Cᵢ é uma classe não genérica, não aninhada
  • O nome de Mₑ é identificador
  • Mₑ é acessível e aplicável quando aplicado aos argumentos como um método estático, conforme mostrado acima
  • Uma identidade implícita, referência ou boxing, boxing ou uma conversão de span existe de expr para o tipo do primeiro parâmetro de Mₑ. A conversão de Span não é considerada quando a resolução de sobrecarga é realizada para uma conversão de grupo de métodos.

Observe que a conversão implícita de span não é considerada para o receptor de extensão nas conversões de grupos de métodos (LDM 2024-07-15), o que faz com que o código abaixo continue operando corretamente ao invés de resultar em um erro de compilação CS1113: Extension method 'E.M<int>(Span<int>, int)' defined on value type 'Span<int>' cannot be used to create delegates:

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 possível trabalho futuro, poderíamos considerar a remoção dessa condição de que a conversão de span não é considerada para o receptor de extensão em conversões de grupos de métodos e, em vez disso, implementar alterações para que o cenário como o acima acabe chamando com êxito a sobrecarga de Span em vez de:

  • O compilador poderia emitir uma conversão que aceitaria a matriz como receptor e realizaria a conversão de span internamente (da mesma forma que o usuário cria manualmente o delegado como x => new int[0].M(x)).
  • Os delegados de valor, se implementados, poderão usar o Span como receptor diretamente.

Variação

O objetivo da seção de variância na conversão de span implícita é replicar uma certa quantidade de covariância para System.ReadOnlySpan<T>. As alterações de runtime seriam necessárias para implementar totalmente a variação por meio de genéricos aqui (consulte .. /csharp-13.0/ref-struct-interfaces.md para usar ref struct tipos em genéricos), mas podemos permitir uma quantidade limitada de covariância por meio do uso de uma API do .NET 9 proposta: https://github.com/dotnet/runtime/issues/96952. Isso permitirá que a linguagem trate System.ReadOnlySpan<T> como se o T fosse declarado como out T em alguns cenários. No entanto, não analisamos essa conversão de variante em todos os cenários de variância e não a adicionamos à definição de variância conversível em §18.2.3.3. Se, no futuro, mudarmos o runtime para entender mais profundamente a variação aqui, podemos fazer a pequena mudança com impacto reduzido para reconhecê-la totalmente na linguagem.

Padrões

Observe que quando ref structs são usados como um tipo em qualquer padrão, somente conversões de identidade são permitidas:

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 { }

Da especificação do operador is-type (§12.12.12.1):

O resultado da operação E is T [...] é um valor booliano que indica se E não é nulo e pode ser convertido com êxito em tipo T por uma conversão de referência, uma conversão de boxing, uma conversão de unboxing, uma conversão de encapsulamento ou uma conversão de desdobramento.

[...]

Se T for um tipo de valor não anulável, o resultado será true se D e T forem do mesmo tipo.

Esse comportamento não muda com esse recurso, portanto, não será possível escrever padrões para Span/ReadOnlySpan, embora padrões semelhantes sejam possíveis para arrays (incluindo variância):

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
}

Geração de código

As conversões sempre existirão, independentemente de algum auxiliar de tempo de execução usado para implementá-las estar presente (LDM 2024-05-13). Se os auxiliares não estiverem presentes, a tentativa de usar a conversão resultará em um erro em tempo de compilação em que um membro necessário do compilador está ausente.

O compilador espera usar os seguintes auxiliares ou equivalentes para implementar as conversões:

Conversão Ajudantes
matriz para Span static implicit operator Span<T>(T[]) (definido em Span<T>)
matriz para ReadOnlySpan static implicit operator ReadOnlySpan<T>(T[]) (definido em ReadOnlySpan<T>)
Span para ReadOnlySpan static implicit operator ReadOnlySpan<T>(Span<T>) (definido em Span<T>) e static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>)
ReadOnlySpan para ReadOnlySpan static ReadOnlySpan<T>.CastUp<TDerived>(ReadOnlySpan<TDerived>)
cadeia de caracteres para ReadOnlySpan static ReadOnlySpan<char> MemoryExtensions.AsSpan(string)

Observe que MemoryExtensions.AsSpan é usado em vez do operador implícito equivalente definido em string. Isso significa que o codegen é diferente entre LangVersions (o operador implícito é usado no C# 13; o método AsSpan estático é usado no C# 14). Por outro lado, a conversão pode ser emitida no .NET Framework (o AsSpan método existe lá, enquanto o string operador não).

A conversão explícita de matriz para (ReadOnly)Span primeiro transforma explicitamente a matriz de origem em uma matriz com o tipo de elemento de destino e, em seguida, para (ReadOnly)Span através do mesmo mecanismo que uma conversão implícita usaria, ou seja, o op_Implicit(T[]) correspondente.

Melhor conversão da expressão

A melhor conversão da expressão (§12.6.4.5) é atualizada para preferir conversões de intervalo implícitas. Isso se baseia em alterações de resolução de sobrecarga de expressões de coleção.

Considerando uma conversão implícita C₁ que converte de uma expressão E para um tipo T₁, e uma conversão implícita C₂ que converte de uma expressão E para um tipo T₂, C₁ é uma conversão melhor do que C₂ se uma das seguintes condições:

  • E é uma expressão de coleção, e C₁ é uma conversão de coleção melhor a partir da expressão do que C₂
  • E não é uma expressão de coleção e uma das seguintes retenções:
    • E corresponde exatamente a T₁ e E não corresponde exatamente a T₂
    • E não corresponde exatamente a nenhum dos T₁ e T₂ e C₁ é uma conversão implícita de span e C₂ não é uma conversão de extensão implícita
    • E corresponde exatamente a ambos ou nenhum de T₁ e T₂, ambos ou nenhum de C₁ e C₂ são uma conversão de span implícita, e T₁ é um destino de conversão melhor do que T₂
  • E é um grupo de métodos, T₁ é compatível com o melhor método do grupo de métodos para conversão C₁e T₂ não é compatível com o melhor método do grupo de métodos para conversão C₂

Melhor meta de conversão

Melhor destino de conversão (§12.6.4.7) é atualizado para preferir ReadOnlySpan<T> sobre Span<T>.

Considerando dois tipos T₁ e T₂, T₁ é um alvo de conversão melhor do que T₂ se uma das seguintes condições for atendida:

  • T₁ é System.ReadOnlySpan<E₁>, T₂ é System.Span<E₂>, e uma conversão de identidade de E₁ para E₂ existe
  • T₁ é System.ReadOnlySpan<E₁>, T₂ é System.ReadOnlySpan<E₂>, e uma conversão implícita de T₁ para T₂ existe e nenhuma conversão implícita de T₂ para T₁ existe
  • Pelo menos um de T₁ ou T₂ não é System.ReadOnlySpan<Eᵢ> e não é System.Span<Eᵢ>, e existe uma conversão implícita de T₁ para T₂ e nenhuma conversão implícita de T₂ para T₁ existe
  • ...

Reuniões de design:

Observações de melhoria

A regra de melhor conversão da expressão deve garantir que sempre que uma sobrecarga se tornar aplicável devido às novas conversões de span, qualquer ambiguidade potencial com outra sobrecarga seja evitada porque a sobrecarga recém-aplicável é preferencial.

Sem essa regra, o código a seguir compilado com êxito no C# 13 resultaria em um erro de ambiguidade no C# 14 devido à nova conversão implícita padrão de matriz para ReadOnlySpan aplicável a um receptor de método de extensão:

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) { }
}

A regra também permite a introdução de novas APIs que anteriormente resultariam em ambiguidades, por exemplo:

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
}

Aviso

Como a regra de melhoria é definida para as conversões de span que existem apenas no LangVersion >= 14, os autores da API não podem adicionar essas novas sobrecargas se quiserem continuar oferecendo suporte aos usuários no LangVersion <= 13. Por exemplo, se o .NET 9 BCL introduzir essas sobrecargas, os usuários que atualizarem para o net9.0 TFM, mas permanecerem em uma LangVersion inferior, poderão enfrentar erros de ambiguidade no código existente. Veja também uma pergunta aberta abaixo.

Inferência de tipo

Atualizamos a seção de inferências de tipo da especificação da seguinte forma (alterações em negrito).

12.6.3.9 Inferências exatas

Uma inferência exatade um tipo Upara um tipo V é feita da seguinte maneira:

  • Se V for não fixadoXᵢ, U será adicionado ao conjunto de limites exatos para Xᵢ.
  • Caso contrário, os conjuntos V₁...Vₑ e U₁...Uₑ serão determinados verificando-se se algum dos seguintes casos se aplica:
    • V é um tipo V₁[...] de matriz e U é um tipo U₁[...] de matriz da mesma classificação
    • V é um Span<V₁> e U é um tipo U₁[] de matriz ou um Span<U₁>
    • V é um ReadOnlySpan<V₁> e U é um tipo U₁[] de matriz ou um Span<U₁> ou ReadOnlySpan<U₁>
    • V é o tipo V₁? e U é o tipo U₁
    • V é um tipo C<V₁...Vₑ> construído e U é um tipo construído C<U₁...Uₑ>
      Se qualquer um desses casos se aplicar, uma inferência exata será feita de cada Uᵢ ao Vᵢ correspondente.
  • Caso contrário, nenhuma inferência será feita.

12.6.3.10 Inferências de limite inferior

Uma inferência de limite inferior de um tipo Upara um tipo V é feita da seguinte maneira:

  • Se V é um dos não fixadosXᵢ, então U é adicionado ao conjunto de limites inferiores para Xᵢ.
  • Caso contrário, se V for o tipo V₁? e U for o tipo U₁? , uma inferência de limite inferior será feita de U₁ para V₁.
  • Caso contrário, os conjuntos U₁...Uₑ e V₁...Vₑ serão determinados verificando-se se algum dos seguintes casos se aplica:
    • V é um tipo V₁[...] de matriz e U é um tipo U₁[...]de matriz da mesma classificação
    • V é um Span<V₁> e U é um tipo U₁[] de matriz ou um Span<U₁>
    • V é um ReadOnlySpan<V₁> e U é um tipo U₁[] de matriz ou um Span<U₁> ou ReadOnlySpan<U₁>
    • V é um de IEnumerable<V₁>, ICollection<V₁>, IReadOnlyList<V₁>>, IReadOnlyCollection<V₁> ou IList<V₁> e U é um tipo de matriz unidimensional U₁[]
    • V é um tipo class, struct, interface ou delegate construído, tipo C<V₁...Vₑ> e há um tipo exclusivo C<U₁...Uₑ> de modo que U (ou, se U for um tipo parameter, sua classe base efetiva ou qualquer membro do conjunto de interface efetivo) seja idêntico a inherits de (direta ou indiretamente) ou implementa (direta ou indiretamente) C<U₁...Uₑ>.
    • (A restrição de "exclusividade" significa que, no caso de interface C<T>{} class U: C<X>, C<Y>{}, nenhuma inferência é feita ao inferir-se de U para C<T> porque U₁ pode ser X ou Y.)
      Se algum desses casos se aplicar, uma inferência será feita de cada Uᵢ para o correspondente Vᵢ da seguinte maneira:
    • Se Uᵢ não for conhecido como um tipo de referência, uma inferência exata será feita
    • Caso contrário, se U for um tipo de matriz, uma inferência de limite inferior será feita: a inferência depende do tipo de V.
      • Se V é um Span<Vᵢ>, então uma inferência exata é feita
      • Se V for um tipo de matriz ou um ReadOnlySpan<Vᵢ>, então uma inferência de limite inferior é feita
    • Caso contrário, se U é um Span<Uᵢ> então a inferência depende do tipo de V:
      • Se V é um Span<Vᵢ>, então uma inferência exata é feita
      • Se V for um ReadOnlySpan<Vᵢ>, uma inferência de limite inferior é feita
    • Caso contrário, se U for um ReadOnlySpan<Uᵢ> e V for um ReadOnlySpan<Vᵢ>, uma inferência de limite inferior será feita:
    • Caso contrário, se V for C<V₁...Vₑ> a inferência dependerá do parâmetro de tipo i-th de C:
      • Se for covariante, então uma inferência de limite inferior é feita.
      • Se for contravariante, será feita uma inferência de limite superior.
      • Se for invariável, uma inferência exata será feita.
  • Caso contrário, nenhuma inferência será feita.

Não há regras para inferência de limite superior porque não seria possível atingi-las. A inferência de tipo nunca começa como de limite superior, ela teria que passar por uma inferência de limite inferior e um parâmetro de tipo de contravariante. Devido à regra "se Uᵢ não for conhecido como um tipo de referência, uma inferência exata será feita", o argumento de tipo de origem não poderá ser Span/ReadOnlySpan (não podem ser tipos de referência). No entanto, a inferência de intervalo de limite superior só se aplicaria se o tipo de origem fosse um Span/ReadOnlySpan, pois teria regras como:

  • U é um Span<U₁> e V é um tipo V₁[] de matriz ou um Span<V₁>
  • U é um ReadOnlySpan<U₁> e V é um tipo V₁[] de matriz ou um Span<V₁> ou ReadOnlySpan<V₁>

Alterações da falha

Como qualquer proposta que altera as conversões de cenários existentes, esta proposta introduz algumas novas alterações significativas. Veja a seguir alguns exemplos:

Chamando Reverse em um array

Chamar x.Reverse() onde x é uma instância do tipo T[] anteriormente se ligava a IEnumerable<T> Enumerable.Reverse<T>(this IEnumerable<T>), enquanto que agora se liga a void MemoryExtensions.Reverse<T>(this Span<T>). Infelizmente, essas APIs são incompatíveis (a última faz a reversão no local e retorna void).

O .NET 10 atenua isso adicionando uma sobrecarga IEnumerable<T> Reverse<T>(this T[])específica da matriz, consulte 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
}

Veja também:

Reunião de design: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#reverse

Ambigüidades

Os exemplos a seguir falharam anteriormente na inferência de tipo para a sobrecarga de Span, mas agora a inferência de tipo de matriz para Span é bem-sucedida, portanto, eles são ambíguos. Para contornar isso, os usuários podem usar .AsSpan() ou os autores da API podem usar OverloadResolutionPriorityAttributeo .

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 está adicionando mais sobrecargas para mitigar isso: https://github.com/xunit/xunit/discussions/3021.

Reunião de design: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#new-ambiguities

Matrizes covariantes

Sobrecargas que aceitam IEnumerable<T> funcionavam em matrizes covariantes, mas sobrecargas que aceitam Span<T> (que preferimos agora) não funcionam, porque a conversão de span gera um ArrayTypeMismatchException para matrizes covariantes. Pode-se argumentar que a sobrecarga Span<T> não deveria existir, deveria utilizar ReadOnlySpan<T> em vez disso. Para contornar isso, os usuários podem usar .AsEnumerable() ou os autores de API podem usar OverloadResolutionPriorityAttribute ou adicionar sobrecarga ReadOnlySpan<T> que é preferencial devido à regra de melhoria.

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ão de design: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-09-11.md#covariant-arrays

Preferindo ReadOnlySpan em vez de Span

A regra de melhoria leva à preferência de sobrecargas ReadOnlySpan em vez de sobrecargas Span para evitar ArrayTypeMismatchException em cenários de matriz de covariantes. Isso pode levar a quebras de compilação em alguns cenários, por exemplo, quando as sobrecargas diferem por seu tipo de retorno:

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.

Árvores de expressão

Sobrecargas que tomam intervalos como MemoryExtensions.Contains são preferenciais em vez de sobrecargas clássicas como Enumerable.Contains, mesmo dentro de árvores de expressão, mas os structs de ref não são compatíveis com o mecanismo de interpretador:

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

Da mesma forma, mecanismos de tradução como o LINQ-to-SQL precisam reagir a isso se os visitantes da árvore de expressão esperarem Enumerable.Contains, pois encontrarão MemoryExtensions.Contains em vez disso.

Veja também:

Reuniões de design:

Conversões definidas pelo usuário por meio de herança

Ao adicionar conversões de intervalo implícitas à lista de conversões implícitas padrão, podemos alterar o comportamento quando as conversões definidas pelo usuário estão envolvidas em uma hierarquia de tipos. Este exemplo mostra essa mudança, em comparação com um cenário de inteiros que já se comporta como o comportamento novo do 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 também: https://github.com/dotnet/roslyn/issues/78314

Pesquisa de método de extensão

Ao permitir conversões de span implícitas na pesquisa de método de extensão, podemos potencialmente alterar qual método de extensão é resolvido pela resolução 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");
        }
    }
}

Perguntas abertas

Regra de melhoria irrestrita

Devemos tornar a regra de melhoria incondicional em LangVersion? Isso permitiria que os autores de APIs adicionassem novas APIs do Span onde existissem equivalentes do IEnumerable sem prejudicar os usuários de versões mais antigas do LangVersion ou de outros compiladores ou linguagens (por exemplo, VB). No entanto, isso significaria que os usuários poderiam obter um comportamento diferente após atualizar o conjunto de ferramentas (sem alterar LangVersion ou TargetFramework):

  • O compilador pode escolher sobrecargas diferentes (tecnicamente uma alteração significativa, mas esperamos que essas sobrecargas tenham comportamento equivalente).
  • Outras falhas, ainda desconhecidas, podem surgir neste momento.

Observe que OverloadResolutionPriorityAttribute não é possível resolver isso completamente porque ele também é ignorado em LangVersions mais antigos. No entanto, deve ser possível usá-lo para evitar ambiguidades do VB onde o atributo deve ser reconhecido.

Ignorar mais conversões definidas pelo usuário

Definimos um conjunto de pares de tipos para os quais há conversões de span implícitas e explícitas definidas pela linguagem. Sempre que existe uma conversão de intervalo definida pelo idioma de T1 para T2, qualquer conversão definida pelo usuário de T1 para T2 é ignorada (independentemente de o intervalo e a conversão definida pelo usuário serem implícitas ou explícitas).

Observe que isso inclui todas as condições, então, por exemplo, não há conversão de span de Span<object> para ReadOnlySpan<string> (há uma conversão de span de Span<T> para ReadOnlySpan<U>, mas deve manter que T : U), portanto, uma conversão definida pelo usuário seria considerada entre esses tipos se existisse (que teria que ser uma conversão especializada como Span<T> para ReadOnlySpan<string> porque os operadores de conversão não podem ter parâmetros genéricos).

Devemos ignorar as conversões definidas pelo usuário também entre outras combinações de tipos array/Span/ReadOnlySpan/string em que não existe conversão de span definida pelo idioma correspondente? Por exemplo, se houver uma conversão definida pelo usuário de ReadOnlySpan<T> para Span<T>, devemos ignorá-la?

Possibilidades de especificação a serem consideradas:

  1. Sempre que existir uma conversão de intervalo de T1 para T2, ignore qualquer conversão definida pelo usuário de T1 para T2ou de T2 para T1.

  2. As conversões definidas pelo usuário não são consideradas ao converter entre

    • qualquer array_type unidimensional e System.Span<T>/System.ReadOnlySpan<T>,
    • qualquer combinação de System.Span<T>/System.ReadOnlySpan<T>,
    • string e System.ReadOnlySpan<char>.
  3. Como acima, mas substituindo o último ponto de marcador por:
    • string e System.Span<char>/System.ReadOnlySpan<char>.
  4. Como acima, mas substituindo o último ponto de marcador por:
    • string e System.Span<T>/System.ReadOnlySpan<T>.

Tecnicamente, a especificação não permite que algumas dessas conversões definidas pelo usuário sejam definidas: não é possível definir um operador definido pelo usuário entre tipos para os quais existe uma conversão não definida pelo usuário (§10.5.2). Mas Roslyn viola intencionalmente essa parte da especificação. E algumas conversões como between Span e string são permitidas de qualquer maneira (não existe conversão definida pelo idioma entre esses tipos).

No entanto, alternativamente a apenas ignorar as conversões, poderíamos não permitir que elas fossem definidas e talvez sair da violação de especificação pelo menos para essas novas conversões de span, ou seja, alterar Roslyn para realmente relatar um erro de tempo de compilação se essas conversões forem definidas (provavelmente, exceto aquelas já definidas pela BCL).

Alternativas

Mantenha as coisas como estão.