Freigeben über


Bessere Konvertierung aus Sammlungs-Ausdrucks-Element

Hinweis

Dieser Artikel ist eine Feature-Spezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.

Es kann einige Abweichungen zwischen der Feature-Spezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.

Weitere Informationen zum Prozess für die Aufnahme von Funktions-Speclets in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

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

Zusammenfassung

Aktualisierungen der besseren Konvertierungsregeln, um konsistenter mit params zu sein und aktuelle Mehrdeutigkeits-Szenarien besser zu behandeln. Zum Beispiel kann ReadOnlySpan<string> vs. ReadOnlySpan<object> derzeit Mehrdeutigkeiten während der Überladungsauflösung für [""] verursachen.

Detailliertes Design

Die Folgenden sind die besseren Konvertierungen aus Ausdrucksregeln. Diese ersetzen die Regeln in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Diese Regeln sind:

Gegeben eine implizite Konvertierung C₁, die von einem Ausdruck E zu einem Typ T₁ konvertiert, und eine implizite Konvertierung C₂, die von einem Ausdruck E zu einem Typ T₂ konvertiert, ist C₁ eine bessere Konvertierung als C₂, wenn eine der folgenden Bedingungen erfüllt ist:

  • E ist ein Sammlungs-Ausdruck, und C₁ ist eine bessere Sammlungs-Konvertierung aus Ausdruck als C₂
  • E ist kein Sammlungs-Ausdruck und eine der folgenden Bedingungen gilt:
    • E stimmt genau mit T₁ überein und E stimmt nicht genau mit T₂ überein
    • E stimmt genau mit beiden oder keinem von T₁ und T₂ überein, und T₁ ist ein besseres Konvertierungsziel als T₂
  • E ist eine Methodengruppe, ...

Wir fügen eine neue Definition für bessere Sammlungs-Konvertierung aus Ausdruck hinzu, wie folgt:

Gegeben:

  • E ist ein Sammlungs-Ausdruck mit Elementausdrücken [EL₁, EL₂, ..., ELₙ]
  • T₁ und T₂ sind Sammlungstypen
  • E₁ ist der Elementtyp von T₁
  • E₂ ist der Elementtyp von T₂
  • CE₁ᵢ sind die Serie von Konvertierungen von ELᵢ zu E₁
  • CE₂ᵢ sind die Serie von Konvertierungen von ELᵢ zu E₂

Wenn es eine Identitätskonvertierung von E₁ zu E₂ gibt, sind die Elementkonvertierungen genauso gut wie die jeweils andere. Andernfalls sind die Elementkonvertierungen zu E₁besser als die Elementkonvertierungen zu E₂, wenn:

  • Für jedes ELᵢ ist CE₁ᵢ mindestens so gut wie CE₂ᵢ, und
  • Es gibt mindestens ein i, bei dem CE₁ᵢ besser ist als CE₂ᵢ, andernfalls ist keine Gruppe von Elementkonvertierungen besser als die andere, und sie sind einander auch nicht überlegen.
    Konvertierungsvergleiche werden mit besseren Konvertierungen aus Ausdruck durchgeführt, wenn ELᵢ kein Spread-Element ist. Wenn ELᵢ ein Spread-Element ist, verwenden wir die bessere Konvertierung vom Elementtyp der Spread-Sammlung jeweils zu E₁ oder E₂.

C₁ ist eine bessere Sammlungs-Konvertierung aus Ausdruck als C₂, wenn:

  • Beide T₁ und T₂ keine Span-Typen sind, und T₁ implizit konvertierbar zu T₂ ist, und T₂ nicht implizit konvertierbar zu T₁ ist, oder
  • E₁ keine Identitätskonvertierung zu E₂ hat, und die Elementkonvertierungen zu E₁besser als die Elementkonvertierungen zu E₂ sind, oder
  • E₁ eine Identitätskonvertierung zu E₂ hat, und eine der folgenden Bedingungen erfüllt ist:
    • T₁ ist System.ReadOnlySpan<E₁>, und T₂ ist System.Span<E₂>, oder
    • T₁ ist System.ReadOnlySpan<E₁> oder System.Span<E₁>, und T₂ ist ein array_or_array_interface mit ElementtypE₂

Andernfalls ist kein Sammlungstyp besser, und das Ergebnis ist mehrdeutig.

Hinweis

Diese Regeln bedeuten, dass Methoden, die Überladungen bereitstellen, die unterschiedliche Elementtypen verwenden und keine Konvertierung zwischen den Sammlungstypen zulassen, für leere Sammlungs-Ausdrücke mehrdeutig sind. Beispiel:

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

M([]); // Ambiguous

Szenarien:

In verständlicher Sprache müssen die Sammlungstypen selbst entweder identisch oder eindeutig besser sein (d.h. List<T> und List<T> sind identisch, List<T> ist eindeutig besser als IEnumerable<T>, und List<T> und HashSet<T> können nicht verglichen werden), und die Elementkonvertierungen für den besseren Sammlungstyp müssen ebenfalls identisch oder besser sein (d.h., wir können nicht zwischen ReadOnlySpan<object> und Span<string> für [""]entscheiden, da der Benutzer diese Entscheidung treffen muss). Weitere Beispiele:

T₁ T₂ E C₁ Konvertierungen C₂ Konvertierungen CE₁ᵢ vs. CE₂ᵢ Ergebnis
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ ist besser List<int> wird ausgewählt
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Nicht zutreffend T₂ ist nicht zutreffend List<int> wird ausgewählt
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Keine Version ist besser Nicht eindeutig
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ ist besser List<byte> wird ausgewählt
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Keine Version ist besser Nicht eindeutig
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ ist besser List<int?> wird ausgewählt
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ ist besser List<short> wird ausgewählt
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ ist besser IEnumerable<int> wird ausgewählt
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ ist besser List<byte> wird ausgewählt
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ ist besser int[] wird ausgewählt
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ ist besser ReadOnlySpan<string> wird ausgewählt
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Nicht zutreffend [Implicit Reference, Identity] T₁ ist nicht zutreffend ReadOnlySpan<object> wird ausgewählt
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ ist besser Span<string> wird ausgewählt
ReadOnlySpan<object> Span<string> [new object()] [Identity] Nicht zutreffend T₁ ist nicht zutreffend ReadOnlySpan<object> wird ausgewählt
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ ist besser ReadOnlySpan<InterpolatedStringHandler> wird ausgewählt
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Aber konstant CE₂ᵢ ist besser ReadOnlySpan<string> wird ausgewählt
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ ist besser ReadOnlySpan<string> wird ausgewählt
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Nicht zutreffend [Interpolated String, Identity] T₁ ist nicht zutreffend ReadOnlySpan<FormattableString> wird ausgewählt
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ ist besser HashSet<short> wird ausgewählt
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ ist besser Span<short> wird ausgewählt

Offene Fragen

In welchem Ausmaß sollten wir ReadOnlySpan/Span über andere typen priorisieren?

Wie derzeit festgelegt, wären die folgenden Überladungen mehrdeutig:

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

Wie weit möchten wir hier gehen? Die List<T>-Variante scheint vernünftig, und Untertypen von List<T> existieren reichlich. Aber die HashSet-Version hat sehr unterschiedliche Semantik. Wie sicher sind wir, dass sie tatsächlich „schlechter“ ist als ReadOnlySpan in dieser API?