Condividi tramite


Conversione migliore dall'elemento di un'espressione di raccolta

Nota

Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.

Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Le differenze sono documentate nelle note pertinenti del meeting di progettazione del linguaggio (LDM) .

Puoi trovare ulteriori informazioni sul processo per l'adozione di speclets di funzionalità nello standard del linguaggio C# nell'articolo sulle specifiche .

Problema del campione: https://github.com/dotnet/csharplang/issues/8374

Sommario

Gli aggiornamenti alle regole di conversione migliori sono più coerenti con paramse gestiscono meglio gli scenari di ambiguità correnti. Ad esempio, ReadOnlySpan<string> vs ReadOnlySpan<object> possono attualmente causare ambiguità durante la risoluzione dell'overload per [""].

Progettazione dettagliata

Di seguito sono riportate le migliori conversioni derivanti da regole di espressione. Queste sostituiscono le regole in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Queste regole sono:

Data una conversione implicita C₁ che converte un'espressione E in un tipo T₁e una conversione implicita C₂ che converte un'espressione E in un tipo T₂, C₁ è una conversione migliore rispetto a C₂ se si verifica una delle seguenti condizioni:

  • E è espressione di raccolta e C₁ è una conversione di raccolta migliore da espressione rispetto a C₂
  • E non è un'espressione di raccolta e si verifica una delle seguenti condizioni:
    • E corrisponde esattamente T₁ e E non corrisponde esattamente T₂
    • E corrisponde esattamente a T₁ e T₂e T₁ è undi destinazione di conversionemigliore di T₂
  • E è un gruppo di metodi, ...

Aggiungiamo una nuova definizione per per una migliore conversione della raccolta dall'espressione, come indicato di seguito:

Dedito:

  • E è un'espressione di raccolta con espressioni degli elementi [EL₁, EL₂, ..., ELₙ]
  • T₁ e T₂ sono tipi di raccolta
  • E₁ è il tipo di elemento di T₁
  • E₂ è il tipo di elemento di T₂
  • CE₁ᵢ sono la serie di conversioni da ELᵢ a E₁
  • CE₂ᵢ sono la serie di conversioni da ELᵢ a E₂

Se è presente una conversione di identità da E₁ a E₂, le conversioni degli elementi sono uguali tra loro. In caso contrario, le conversioni degli elementi in E₁ sono migliori delle conversioni degli elementi in E₂ se:

  • Per ogni ELᵢ, CE₁ᵢ è almeno buono quanto CE₂ᵢe
  • C'è almeno un i dove CE₁ᵢ è meglio di CE₂ᵢ Altrimenti, nessun set di conversioni di elementi è migliore dell'altro, e non sono anche buono come l'altro.
    I confronti di conversione vengono eseguiti usando una conversione migliore dall'espressione se ELᵢ non è un elemento spread. Se ELᵢ è un elemento spread, usiamo una conversione migliore dal tipo di elemento della raccolta spread a E₁ o E₂, rispettivamente.

C₁ è una conversione migliore della raccolta dall'espressione rispetto a C₂ se:

  • Sia T₁ che T₂ non sono tipi di intervallo associati a , mentre T₁ è convertibile implicitamente in T₂e T₂ non lo è in T₁, oppure
  • E₁ non dispone di una conversione di identità in E₂e le conversioni degli elementi in E₁ sono migliori rispetto alle conversioni di elementi in E₂o
  • E₁ ha una conversione di identità in E₂e vale una delle seguenti:
    • T₁ è System.ReadOnlySpan<E₁>e T₂ è System.Span<E₂>o
    • T₁ è System.ReadOnlySpan<E₁> o System.Span<E₁>e T₂ è un array_or_array_interface con tipo di elemento E₂

In caso contrario, nessun tipo di raccolta è migliore e il risultato è ambiguo.

Nota

Queste regole stabiliscono che i metodi che offrono sovraccarichi accettando diversi tipi di elementi e senza una conversione tra i tipi di raccolta risultano ambigui per espressioni di raccolta vuote. Ad esempio:

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

M([]); // Ambiguous

Scenari:

In inglese normale, I tipi di raccolta stessi devono essere uguali o non ambigui (ad esempio, List<T> e List<T> sono uguali, List<T> è preferibile in modo univoco rispetto a IEnumerable<T>e List<T> e HashSet<T> non possono essere confrontati), e le conversioni degli elementi per il tipo di raccolta migliore devono essere uguali o migliori (ad esempio, non è possibile decidere tra ReadOnlySpan<object> e Span<string> per [""], l'utente deve prendere tale decisione. Altri esempi sono:

T₁ T₂ E conversioni C₁ conversioni C₂ CE₁ᵢ e CE₂ᵢ Risultato
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ è meglio List<int> viene selezionato
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Non applicabile T₂ non è applicabile List<int> viene selezionato
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Nessuno dei due è migliore Ambiguo
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ è meglio List<byte> viene selezionato
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Nessuno dei due è migliore Ambiguo
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ è meglio List<int?> viene selezionato
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ è meglio List<short> viene selezionato
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ è meglio IEnumerable<int> viene selezionato
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ è meglio List<byte> viene selezionato
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ è meglio int[] viene selezionato
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ è meglio ReadOnlySpan<string> viene selezionato
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Non applicabile [Implicit Reference, Identity] T₁ non è applicabile ReadOnlySpan<object> viene selezionato
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ è meglio Span<string> viene selezionato
ReadOnlySpan<object> Span<string> [new object()] [Identity] Non applicabile T₁ non è applicabile ReadOnlySpan<object> viene selezionato
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ è meglio ReadOnlySpan<InterpolatedStringHandler> viene selezionato
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Ma costante CE₂ᵢ è meglio ReadOnlySpan<string> viene selezionato
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ è meglio ReadOnlySpan<string> viene selezionato
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Non applicabile [Interpolated String, Identity] T₁ non è applicabile ReadOnlySpan<FormattableString> viene selezionato
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ è meglio HashSet<short> viene selezionato
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ è meglio Span<short> viene selezionato

Domande aperte

Fino a che punto dovremmo dare priorità a ReadOnlySpan/Span rispetto ad altri tipi?

Come specificato oggi, le seguenti sovraccariche sarebbero ambigue:

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

Quanto lontano vogliamo andare qui? La variante List<T> sembra ragionevole e i sottotipi di List<T> esistono aplenty. Ma la versione HashSet ha una semantica molto diversa, come si è certi che sia effettivamente "peggio" rispetto a ReadOnlySpan in questa API?