Dela via


Bättre konvertering från samlingsuttryckselement

Anteckning

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta anteckningarna från LDM (Language Design Meeting) .

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-problem: https://github.com/dotnet/csharplang/issues/8374

Sammanfattning

Uppdateringar av de bättre konverteringsreglerna för att vara mer konsekventa med paramsoch bättre hantera aktuella tvetydighetsscenarier. Till exempel kan ReadOnlySpan<string> vs ReadOnlySpan<object> för närvarande orsaka tvetydigheter vid överbelastningslösning för [""].

Detaljerad design

Följande är den bättre konverteringen från uttrycksregler. Dessa ersätter reglerna i https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Dessa regler är:

Med en implicit konvertering C₁ som konverterar från ett uttryck E till en typ T₁och en implicit konvertering C₂ som konverterar från ett uttryck E till en typ T₂är C₁ en bättre konvertering än C₂ om något av följande gäller:

  • E är ett samlingsuttryckoch C₁ är en bättre samlingskonvertering från uttryck än C₂
  • E är inte ett samlingsuttryck och något av följande gäller:
    • E matchar exakt T₁ och E matchar inte exakt T₂
    • E matchar exakt både eller inget av T₁ och T₂, och T₁ är ett bättre konverteringsmål än T₂
  • E är en metodgrupp, ...

Vi lägger till en ny definition för bättre samlingskonvertering från uttrycket, enligt följande:

Given:

  • E är ett samlingsuttryck med elementuttryck [EL₁, EL₂, ..., ELₙ]
  • T₁ och T₂ är samlingstyper
  • E₁ är elementtypen för T₁
  • E₂ är elementtypen för T₂
  • CE₁ᵢ är en serie konverteringar från ELᵢ till E₁
  • CE₂ᵢ är en serie konverteringar från ELᵢ till E₂

Om det finns en identitetskonvertering från E₁ till E₂är elementkonverteringarna lika bra som varandra. Annars är elementkonverteringarna till E₁bättre än elementkonverteringarnaE₂ om:

  • För varje ELᵢär CE₁ᵢ minst lika bra som CE₂ᵢoch
  • Det finns minst en i där CE₁ᵢ är bättre än CE₂ᵢ Annars är ingen uppsättning elementkonverteringar bättre än den andra, och de är också inte lika bra som varandra.
    Konverteringsjämförelser görs med bättre konvertering från uttryck om ELᵢ inte är ett spridningselement. Om ELᵢ är ett spridningselement använder vi bättre konvertering från elementtypen för spridningssamlingen till E₁ respektive E₂.

C₁ är en bättre samlingskonvertering från uttryck än C₂ om:

  • Varken T₁ eller T₂ är spantyper, och T₁ kan implicit konverteras till T₂, medan T₂ inte kan implicit konverteras till T₁eller
  • E₁ har ingen identitetskonvertering till E₂och elementkonverteringarna till E₁ är bättre än elementkonverteringarna till E₂, eller
  • E₁ har en identitetskonvertering till E₂och något av följande gäller:
    • T₁ är System.ReadOnlySpan<E₁>och T₂ är System.Span<E₂>, eller
    • T₁ är System.ReadOnlySpan<E₁> eller System.Span<E₁>, och T₂ är ett array_or_array_interface med elementtypE₂

Annars är ingen av samlingstyperna bättre och resultatet är tvetydigt.

Anteckning

Dessa regler innebär att metoder som exponerar överlagringar som tar olika elementtyper och utan konvertering mellan samlingstyperna är tvetydiga för tomma samlingsuttryck. Som exempel:

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

M([]); // Ambiguous

Scenarier:

På vanlig engelska, själva samlingstyperna måste vara identiska eller entydigt bättre (dvs. List<T> och List<T> är desamma, List<T> är entydigt bättre än IEnumerable<T>, och List<T> och HashSet<T> kan inte jämföras), och elementkonverteringarna för den bättre samlingstypen måste också vara desamma eller bättre (dvs. vi kan inte bestämma mellan ReadOnlySpan<object> och Span<string> för [""], måste användaren fatta det beslutet). Fler exempel på detta är:

T₁ T₂ E C₁ konverteringar C₂ konverteringar CE₁ᵢ jämfört med CE₂ᵢ Utfall
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ är bättre List<int> väljs
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Ej tillämpligt T₂ är inte tillämpligt List<int> väljs
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Ingen av dem är bättre Tvetydig
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ är bättre List<byte> väljs
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Ingen av dem är bättre Tvetydig
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ är bättre List<int?> väljs
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ är bättre List<short> väljs
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ är bättre IEnumerable<int> väljs
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ är bättre List<byte> väljs
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ är bättre int[] väljs
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ är bättre ReadOnlySpan<string> väljs
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Ej tillämpligt [Implicit Reference, Identity] T₁ är inte tillämpligt ReadOnlySpan<object> väljs
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ är bättre Span<string> väljs
ReadOnlySpan<object> Span<string> [new object()] [Identity] Ej tillämpligt T₁ är inte tillämpligt ReadOnlySpan<object> väljs
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ är bättre ReadOnlySpan<InterpolatedStringHandler> väljs
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Men konstant CE₂ᵢ är bättre ReadOnlySpan<string> väljs
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ är bättre ReadOnlySpan<string> väljs
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Ej tillämpligt [Interpolated String, Identity] T₁ är inte tillämpligt ReadOnlySpan<FormattableString> väljs
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ är bättre HashSet<short> väljs
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ är bättre Span<short> väljs

Öppna frågor

Hur långt ska vi prioritera ReadOnlySpan/Span framför andra typer?

Som vi har angett i dag skulle följande överbelastningar vara tvetydiga:

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

Hur långt vill vi gå hit? Den List<T> varianten verkar rimlig, och undertyper av List<T> finns aplenty. Men den HashSet versionen har mycket olika semantik, hur säkra är vi på att det faktiskt är "värre" än ReadOnlySpan i det här API:et?