Delen via


Betere conversie van het expressie-element van de verzameling

Notitie

Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.

Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante LDM-notities (Language Design Meeting).

Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.

Kampioenprobleem https://github.com/dotnet/csharplang/issues/8374

Samenvatting

Updates voor de betere conversieregels om consistenter te zijn met paramsen om de huidige dubbelzinnigheidsscenario's beter af te handelen. Zo kan ReadOnlySpan<string> versus ReadOnlySpan<object> momenteel ambiguïteit veroorzaken tijdens overbelastingsresolutie voor [""].

Gedetailleerd ontwerp

Hier volgen de beste conversies van expressieregels. Deze vervangen de regels in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Deze regels zijn:

Gezien een impliciete conversie C₁ die wordt geconverteerd van een expressie E naar een type T₁en een impliciete conversie C₂ die converteert van een expressie E naar een type T₂, is C₁ een betere conversie dan C₂ als een van de volgende bewaringen geldt:

  • E is een verzamelingsexpressieen C₁ is een betere verzamelingsconversie van expressies dan C₂
  • E is geen verzameluitdrukking en een van de volgende situaties geldt:
    • E komt exact overeen met T₁ en E komt niet exact overeen met T₂
    • E komt exact overeen met zowel T₁ als T₂, of met geen van beide, en T₁ is een beter conversiedoel dan T₂
  • E is een methodegroep, ...

We voegen als volgt een nieuwe definitie toe voor betere verzamelingsconversie van expressies:

Gegeven:

  • E is een verzamelingsexpressie met elementexpressies [EL₁, EL₂, ..., ELₙ]
  • T₁ en T₂ zijn verzamelingstypen
  • E₁ is het elementtype van T₁
  • E₂ is het elementtype van T₂
  • CE₁ᵢ zijn de reeks conversies van ELᵢ tot E₁
  • CE₂ᵢ zijn de reeks conversies van ELᵢ tot E₂

Als er een identiteitsconversie is van E₁ naar E₂, zijn de elementconversies net zo goed als elkaar. Anders zijn de elementconversies naar E₁ beter dan de elementconversies naar E₂ als:

  • Voor elke ELᵢis CE₁ᵢ minstens zo goed als CE₂ᵢ, en
  • Er is ten minste één i waar CE₁ᵢ beter is dan CE₂ᵢ Anders is geen van beide set elementconversies beter dan de andere, en ze zijn ook niet zo goed als elkaar.
    Conversievergelijkingen worden gemaakt met een betere conversie van expressies als ELᵢ geen verspreid element is. Als ELᵢ een verspreid element is, gebruiken we respectievelijk een betere conversie van het elementtype van de verspreidingsverzameling naar E₁ of E₂.

C₁ is een betere verzamelingsconversie van expressies dan C₂ als:

  • Zowel T₁ als T₂ zijn geen span types, en T₁ is impliciet converteerbaar naar T₂, en T₂ is niet impliciet converteerbaar naar T₁, of
  • E₁ heeft geen identiteitsconversie naar E₂en de elementconversies naar E₁ zijn beter dan de elementconversies naar E₂, of
  • E₁ heeft een identiteitsconversie naar E₂en een van de volgende geldt:
    • T₁ is System.ReadOnlySpan<E₁>en T₂ is System.Span<E₂>, of...
    • T₁ is gelijk aan System.ReadOnlySpan<E₁> of System.Span<E₁>, en T₂ is een array_or_array_interface met het elementtypeE₂

Anders is geen van beide verzamelingstypen beter en is het resultaat dubbelzinnig.

Notitie

Deze regels betekenen dat methoden die overbelastingen blootstellen die verschillende elementtypen gebruiken en zonder conversie tussen de verzamelingstypen dubbelzinnig zijn voor lege verzamelingsexpressies. Als voorbeeld:

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

M([]); // Ambiguous

Scenario 's:

In het Engels moeten de verzamelingstypen zelf hetzelfde zijn of ondubbelzinnig beter zijn (d.w.w.v. List<T> en List<T> hetzelfde zijn, List<T> is ondubbelzinnig beter dan IEnumerable<T>, en List<T> en HashSet<T> niet kunnen worden vergeleken), en de elementconversies voor het betere verzamelingstype moeten ook hetzelfde of beter zijn (we kunnen niet kiezen tussen ReadOnlySpan<object> en Span<string> voor [""], de gebruiker moet die beslissing nemen). Meer voorbeelden hiervan zijn:

T₁ T₂ E C₁ conversies C₂ conversies CE₁ᵢ versus CE₂ᵢ Resultaat
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ is beter List<int> wordt gekozen
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Niet van toepassing T₂ is niet van toepassing List<int> wordt gekozen
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Geen van beide is beter Dubbelzinnig
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ is beter List<byte> wordt gekozen
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Geen van beide is beter Dubbelzinnig
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ is beter List<int?> wordt gekozen
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ is beter List<short> wordt gekozen
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ is beter IEnumerable<int> wordt gekozen
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ is beter List<byte> wordt gekozen
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ is beter int[] wordt gekozen
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ is beter ReadOnlySpan<string> wordt gekozen
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Niet van toepassing [Implicit Reference, Identity] T₁ is niet van toepassing ReadOnlySpan<object> wordt gekozen
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ is beter Span<string> wordt gekozen
ReadOnlySpan<object> Span<string> [new object()] [Identity] Niet van toepassing T₁ is niet van toepassing ReadOnlySpan<object> wordt gekozen
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ is beter ReadOnlySpan<InterpolatedStringHandler> wordt gekozen
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Maar constante CE₂ᵢ is beter ReadOnlySpan<string> wordt gekozen
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ is beter ReadOnlySpan<string> wordt gekozen
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Niet van toepassing [Interpolated String, Identity] T₁ is niet van toepassing ReadOnlySpan<FormattableString> wordt gekozen
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ is beter HashSet<short> wordt gekozen
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ is beter Span<short> wordt gekozen

Open vragen

Hoe ver moeten we prioriteit geven aan ReadOnlySpan/Span boven andere typen?

Zoals vandaag is opgegeven, zijn de volgende overbelastingen dubbelzinnig:

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

Hoe ver willen we hierheen? De List<T> variant lijkt redelijk, en subtypen van List<T> bestaan in overvloed. Maar de HashSet versie heeft heel andere semantiek, hoe zeker zijn we dat het eigenlijk 'erger' is dan ReadOnlySpan in deze API?