Partilhar via


Melhor conversão de elemento de expressão da coleção

Observação

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

Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da Language Design Meeting (LDM).

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 .

Questão campeã: https://github.com/dotnet/csharplang/issues/8374

Resumo

Atualizações para as melhores regras de conversão para ser mais consistente com paramse lidar melhor com os cenários de ambiguidade atuais. Por exemplo, ReadOnlySpan<string> vs ReadOnlySpan<object> pode atualmente causar ambiguidades durante a resolução de sobrecarga para [""].

Projeto Detalhado

A seguir estão as melhores conversões a partir das regras de expressão. Estas substituem as regras em https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Estas regras são:

Dado um C₁ de conversão implícito que converte de um E de expressão para um tipo T₁, e um C₂ de conversão implícito que converte de um E de expressão para um tipo T₂, C₁ é umde conversãomelhor do que C₂ se uma das seguintes opções se mantiver:

  • E é uma expressão de coleção , e C₁ é uma conversão de coleção melhor da expressão do que a coleção C₂
  • E não é uma expressão de coleção e uma das seguintes condições é verdadeira:
    • E corresponde exatamente T₁ e E não corresponde exatamente a T₂
    • E corresponde exatamente a ambos ou nenhum dos T₁ e T₂, e T₁ é um alvo de conversão melhor do que T₂
  • E é um grupo de métodos, ...

Adicionamos uma nova definição para para uma melhor conversão da coleção a partir da expressão, conforme segue:

Dado:

  • E é uma expressão de coleção com expressões de elemento [EL₁, EL₂, ..., ELₙ]
  • T₁ e T₂ são tipos de coleção
  • E₁ é o tipo de elemento de T₁
  • E₂ é o tipo de elemento de T₂
  • CE₁ᵢ são as séries de conversões de ELᵢ para E₁
  • CE₂ᵢ são as séries de conversões de ELᵢ para E₂

Se houver uma conversão de identidade de E₁ para E₂, então as conversões de elementos são tão boas quanto as outras. Caso contrário, as conversões de elemento para E₁ são melhores do que as conversões de elemento para E₂ se:

  • Para cada ELᵢ, CE₁ᵢ é pelo menos tão bom quanto CE₂ᵢ, e
  • Há pelo menos um i onde CE₁ᵢ é melhor do que CE₂ᵢ Caso contrário, nenhum conjunto de conversões de elementos é melhor do que o outro, e eles também não são tão bons quanto os outros.
    As comparações de conversão são feitas usando uma melhor conversão da expressão se ELᵢ não for um elemento spread. Se ELᵢ for um elemento spread, usaremos uma melhor conversão do tipo de elemento da coleção spread para E₁ ou E₂, respectivamente.

C₁ é uma melhor conversão de coleção de expressão do que C₂ se:

  • Tanto T₁ quanto T₂ não são tipos de span , e T₁ é implicitamente conversível para T₂, e T₂ não é implicitamente conversível para T₁ou
  • E₁ não tem uma conversão de identidade para E₂, e as conversões de elemento para E₁ são melhores do que as conversões de elemento para E₂, ou
  • E₁ tem uma conversão de identidade para E₂e uma das seguintes é verdadeira:
    • T₁ é System.ReadOnlySpan<E₁>e T₂ é System.Span<E₂>, ou
    • T₁ é System.ReadOnlySpan<E₁> ou System.Span<E₁>e T₂ é um array_or_array_interface com tipo de elementoE₂

Caso contrário, nenhum dos tipos de recolha é melhor e o resultado é ambíguo.

Observação

Essas regras significam que os métodos que expõem sobrecargas que usam tipos de elementos diferentes e sem uma conversão entre os tipos de coleção são ambíguos para expressões de coleção vazias. A título de exemplo:

public void M(ReadOnlySpan<int> ros) { ... }
public void M(Span<int?> span) { ... }

M([]); // Ambiguous

Cenários:

Em inglês simples, os tipos de coleção em si devem ser os mesmos, ou inequivocamente melhores (ou seja, List<T> e List<T> são os mesmos, List<T> é inequivocamente melhor do que IEnumerable<T>, e List<T> e HashSet<T> não podem ser comparados), e as conversões de elementos para o melhor tipo de coleção também devem ser as mesmas ou melhores (ou seja, não podemos decidir entre ReadOnlySpan<object> e Span<string> para [""], o usuário precisa tomar essa decisão). Mais exemplos disso são:

T₁ T₂ E C₁ Conversões C₂ Conversões CE₁ᵢ vs CE₂ᵢ Resultado
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ é melhor List<int> é escolhida
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Não aplicável T₂ não é aplicável List<int> é escolhida
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Nem é melhor Ambíguo
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ é melhor List<byte> é escolhida
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Nem é melhor Ambíguo
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ é melhor List<int?> é escolhida
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ é melhor List<short> é escolhida
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ é melhor IEnumerable<int> é escolhida
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ é melhor List<byte> é escolhida
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ é melhor int[] é escolhida
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ é melhor ReadOnlySpan<string> é escolhida
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Não aplicável [Implicit Reference, Identity] T₁ não é aplicável ReadOnlySpan<object> é escolhida
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ é melhor Span<string> é escolhida
ReadOnlySpan<object> Span<string> [new object()] [Identity] Não aplicável T₁ não é aplicável ReadOnlySpan<object> é escolhida
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ é melhor ReadOnlySpan<InterpolatedStringHandler> é escolhida
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Mas constante CE₂ᵢ é melhor ReadOnlySpan<string> é escolhida
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ é melhor ReadOnlySpan<string> é escolhida
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Não aplicável [Interpolated String, Identity] T₁ não é aplicável ReadOnlySpan<FormattableString> é escolhida
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ é melhor HashSet<short> é escolhida
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ é melhor Span<short> é escolhida

Perguntas abertas

Até que ponto devemos priorizar ReadOnlySpan/Span sobre outros tipos?

Tal como especificado hoje, as seguintes sobrecargas seriam ambíguas:

C.M1(["Hello world"]); // Ambiguous, no tiebreak between ROS and List
C.M2(["Hello world"]); // Ambiguous, no tiebreak between Span and List

C.M3(["Hello world"]); // Ambiguous, no tiebreak between ROS and MyList.

C.M4(["Hello", "Hello"]); // Ambiguous, no tiebreak between ROS and HashSet. Created collections have different contents

class C
{
    public static void M1(ReadOnlySpan<string> ros) {}
    public static void M1(List<string> list) {}

    public static void M2(Span<string> ros) {}
    public static void M2(List<string> list) {}

    public static void M3(ReadOnlySpan<string> ros) {}
    public static void M3(MyList<string> list) {}

    public static void M4(ReadOnlySpan<string> ros) {}
    public static void M4(HashSet<string> hashset) {}
}

class MyList<T> : List<T> {}

Até onde queremos ir aqui? A variante List<T> parece razoável, e subtipos de List<T> existem muitos. Mas a versão HashSet tem semânticas muito diferentes, quão certos temos de que é realmente "pior" do que ReadOnlySpan nesta API?