Compartilhar via


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

Nota

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele 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 discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).

Você pode aprender mais sobre o processo de adoção de especificações compactas de recursos no padrão da linguagem C# no artigo sobre as especificações de .

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

Resumo

Atualiza as melhores regras de conversão para serem mais consistentes com o paramse lidar de forma mais eficaz com os atuais cenários de ambiguidade. Por exemplo, a comparação entre ReadOnlySpan<string> e ReadOnlySpan<object> no momento pode causar ambiguidades durante a resolução de sobrecarga para [""].

Design detalhado

A seguir, as melhores conversões das regras de expressões. Elas substituem as regras no https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Estas regras sã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 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 corresponde exatamente a ambos ou a nenhum de 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 visando a melhor conversão de coleção da expressão, da seguinte maneira:

Considerando:

  • 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₁ᵢ é a série de conversões de ELᵢ para E₁
  • CE₂ᵢ é a série de conversões de ELᵢ para E₂

Se houver uma conversão de identidade de E₁ para E₂, as conversões de elementos serão tão boas quanto as outras. Caso contrário, as conversões de elemento em E₁ serã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 uns aos outros.
    Comparações de conversão são feitas usando uma conversão melhor da expressão se ELᵢ não for um elemento de propagação. Se ELᵢ for um elemento de distribuição, usaremos uma conversão melhor do tipo de elemento da coleção de spread para E₁ ou E₂, respectivamente.

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

  • Tanto T₁ quanto T₂ não são tipos de intervalo, e T₁ é implicitamente conversível para T₂, e T₂ não é implicitamente conversível para T₁, ou seja,
  • E₁ não tem uma conversão de identidade para E₂e as conversões de elemento em 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 condições se aplica:
    • 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 o tipo de elementoE₂

Caso contrário, nenhum tipo de coleção é melhor e o resultado é ambíguo.

Nota

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. Como exemplo:

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

M([]); // Ambiguous

Cenários:

Em inglês sem formatação, os próprios tipos de coleção devem ser iguais, ou inequívocamente melhor (ou seja, List<T> e List<T> são iguais, List<T> é inequívocamente 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 iguais 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₁ᵢ versus CE₂ᵢ Resultado
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ é melhor List<int> é escolhido
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Não aplicável T₂ não é aplicável List<int> é escolhido
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Nenhum dos dois é melhor Ambíguo
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ é melhor List<byte> é escolhido
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Nenhum dos dois é 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?> é escolhido
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ é melhor List<short> é escolhido
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ é melhor IEnumerable<int> é escolhido
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ é melhor List<byte> é escolhido
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ é melhor int[] é escolhido
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ é melhor ReadOnlySpan<string> é escolhido
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Não aplicável [Implicit Reference, Identity] T₁ não é aplicável ReadOnlySpan<object> é escolhido
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ é melhor Span<string> é escolhido
ReadOnlySpan<object> Span<string> [new object()] [Identity] Não aplicável T₁ não é aplicável ReadOnlySpan<object> é escolhido
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ é melhor ReadOnlySpan<InterpolatedStringHandler> é escolhido
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - mas constante CE₂ᵢ é melhor ReadOnlySpan<string> é escolhido
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ é melhor ReadOnlySpan<string> é escolhido
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Não aplicável [Interpolated String, Identity] T₁ não é aplicável ReadOnlySpan<FormattableString> é escolhido
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ é melhor HashSet<short> é escolhido
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ é melhor Span<short> é escolhido

Perguntas abertas

Até que ponto devemos priorizar ReadOnlySpan/Span em relação a outros tipos?

Conforme 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? A variante List<T> parece razoável, e os subtipos de List<T> existem em abundância. Mas a versão HashSet tem semântica muito diferente, quão certo temos de que é realmente "pior" do que ReadOnlySpan nesta API?