Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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_typeunidimensional com o tipo de elementoEiaSystem.Span<Ei> - De qualquer
array_typeunidimensional com tipo de elementoEiparaSystem.ReadOnlySpan<Ui>, desde queEiseja conversível em covariância (§18.2.3.3) paraUi. - De
System.Span<Ti>paraSystem.ReadOnlySpan<Ui>, desde queTiseja conversível em covariância (§18.2.3.3) paraUi - De
System.ReadOnlySpan<Ti>paraSystem.ReadOnlySpan<Ui>, desde queTiseja conversível em covariância (§18.2.3.3) paraUi - De
stringaSystem.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
TiparaSystem.Span<Ui>ouSystem.ReadOnlySpan<Ui>desde que exista uma conversão de referência explícita deTiparaUi.
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ₑé identificadorMₑé 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 deMₑ. 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
Spancomo 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 seEnão é nulo e pode ser convertido com êxito em tipoTpor 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
Tfor um tipo de valor não anulável, o resultado serátrueseDeTforem 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ãoEpara um tipoT₁, e uma conversão implícitaC₂que converte de uma expressãoEpara um tipoT₂,C₁é uma conversão melhor do queC₂se uma das seguintes condições:
Eé uma expressão de coleção, eC₁é uma conversão de coleção melhor a partir da expressão do queC₂Enão é uma expressão de coleção e uma das seguintes retenções:
Ecorresponde exatamente aT₁eEnão corresponde exatamente aT₂Enão corresponde exatamente a nenhum dosT₁eT₂eC₁é uma conversão implícita de span eC₂não é uma conversão de extensão implícitaEcorresponde exatamente a ambos ou nenhum deT₁eT₂, ambos ou nenhum deC₁eC₂são uma conversão de span implícita, eT₁é um destino de conversão melhor do queT₂Eé um grupo de métodos,T₁é compatível com o melhor método do grupo de métodos para conversãoC₁eT₂não é compatível com o melhor método do grupo de métodos para conversãoC₂
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₁eT₂,T₁é um alvo de conversão melhor do queT₂se uma das seguintes condições for atendida:
T₁éSystem.ReadOnlySpan<E₁>,T₂éSystem.Span<E₂>, e uma conversão de identidade deE₁paraE₂existeT₁éSystem.ReadOnlySpan<E₁>,T₂éSystem.ReadOnlySpan<E₂>, e uma conversão implícita deT₁paraT₂existe e nenhuma conversão implícita deT₂paraT₁existe- Pelo menos um de
T₁ouT₂não éSystem.ReadOnlySpan<Eᵢ>e não éSystem.Span<Eᵢ>, e existe uma conversão implícita deT₁paraT₂e nenhuma conversão implícita deT₂paraT₁existe- ...
Reuniões de design:
- 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
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 tipoVé feita da seguinte maneira:
- Se
Vfor não fixadoXᵢ,Userá adicionado ao conjunto de limites exatos paraXᵢ.- Caso contrário, os conjuntos
V₁...VₑeU₁...Uₑserão determinados verificando-se se algum dos seguintes casos se aplica:
Vé um tipoV₁[...]de matriz eUé um tipoU₁[...]de matriz da mesma classificaçãoVé umSpan<V₁>eUé um tipoU₁[]de matriz ou umSpan<U₁>Vé umReadOnlySpan<V₁>eUé um tipoU₁[]de matriz ou umSpan<U₁>ouReadOnlySpan<U₁>Vé o tipoV₁?eUé o tipoU₁Vé um tipoC<V₁...Vₑ>construído eUé um tipo construídoC<U₁...Uₑ>
Se qualquer um desses casos se aplicar, uma inferência exata será feita de cadaUᵢaoVᵢ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 tipoVé feita da seguinte maneira:
- Se
Vé um dos não fixadosXᵢ, entãoUé adicionado ao conjunto de limites inferiores paraXᵢ.- Caso contrário, se
Vfor o tipoV₁?eUfor o tipoU₁?, uma inferência de limite inferior será feita deU₁paraV₁.- Caso contrário, os conjuntos
U₁...UₑeV₁...Vₑserão determinados verificando-se se algum dos seguintes casos se aplica:
Vé um tipoV₁[...]de matriz eUé um tipoU₁[...]de matriz da mesma classificaçãoVé umSpan<V₁>eUé um tipoU₁[]de matriz ou umSpan<U₁>Vé umReadOnlySpan<V₁>eUé um tipoU₁[]de matriz ou umSpan<U₁>ouReadOnlySpan<U₁>Vé um deIEnumerable<V₁>,ICollection<V₁>,IReadOnlyList<V₁>>,IReadOnlyCollection<V₁>ouIList<V₁>eUé um tipo de matriz unidimensionalU₁[]Vé um tipoclass,struct,interfaceoudelegateconstruído, tipoC<V₁...Vₑ>e há um tipo exclusivoC<U₁...Uₑ>de modo queU(ou, seUfor um tipoparameter, sua classe base efetiva ou qualquer membro do conjunto de interface efetivo) seja idêntico ainheritsde (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 deUparaC<T>porqueU₁pode serXouY.)
Se algum desses casos se aplicar, uma inferência será feita de cadaUᵢpara o correspondenteVᵢ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
Ufor um tipo de matriz,uma inferência de limite inferior será feita: a inferência depende do tipo deV.
- Se
Vé umSpan<Vᵢ>, então uma inferência exata é feita- Se
Vfor um tipo de matriz ou umReadOnlySpan<Vᵢ>, então uma inferência de limite inferior é feita- Caso contrário, se
Ué umSpan<Uᵢ>então a inferência depende do tipo deV:
- Se
Vé umSpan<Vᵢ>, então uma inferência exata é feita- Se
Vfor umReadOnlySpan<Vᵢ>, uma inferência de limite inferior é feita- Caso contrário, se
Ufor umReadOnlySpan<Uᵢ>eVfor umReadOnlySpan<Vᵢ>, uma inferência de limite inferior será feita:- Caso contrário, se
VforC<V₁...Vₑ>a inferência dependerá do parâmetro de tipoi-thdeC:
- 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é umSpan<U₁>eVé um tipoV₁[]de matriz ou umSpan<V₁>Ué umReadOnlySpan<U₁>eVé um tipoV₁[]de matriz ou umSpan<V₁>ouReadOnlySpan<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:
- 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ã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:
- 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
Reuniões de design:
- 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
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:
-
Sempre que existir uma conversão de intervalo de
T1paraT2, ignore qualquer conversão definida pelo usuário deT1paraT2ou deT2paraT1. -
As conversões definidas pelo usuário não são consideradas ao converter entre
- qualquer
array_typeunidimensional eSystem.Span<T>/System.ReadOnlySpan<T>, - qualquer combinação de
System.Span<T>/System.ReadOnlySpan<T>, -
stringeSystem.ReadOnlySpan<char>.
- qualquer
- Como acima, mas substituindo o último ponto de marcador por:
-
stringeSystem.Span<char>/System.ReadOnlySpan<char>.
-
- Como acima, mas substituindo o último ponto de marcador por:
-
stringeSystem.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.
C# feature specifications