Partager via


Meilleure conversion à partir de l’élément d’expression de collection

Remarque

Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Elle inclut les changements de spécification proposés, ainsi que les informations nécessaires à la conception et au développement de la fonctionnalité. Ces articles sont publiés jusqu'à ce que les changements proposés soient finalisés et incorporés dans la spécification ECMA actuelle.

Il peut y avoir des divergences entre la spécification de la fonctionnalité et l'implémentation réalisée. Ces différences sont capturées dans les notes de réunion de conception de langage (LDM) pertinentes .

Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications.

Problème de champion : https://github.com/dotnet/csharplang/issues/8374

Résumé

Mises à jour vers les meilleures règles de conversion pour être plus cohérentes avec params, et mieux gérer les scénarios d’ambiguïté actuels. Par exemple, ReadOnlySpan<string> vs ReadOnlySpan<object> peut actuellement provoquer des ambiguïtés pendant la résolution de surcharge pour [""].

Conception détaillée

Voici la meilleure conversion à partir de règles d’expression. Celles-ci remplacent les règles dans https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Il s’agit des règles suivantes :

Étant donné une conversion implicite C₁ qui convertit d’une expression E en type T₁, et une conversion implicite C₂ qui convertit d’une expression E en type T₂, C₁ est une meilleure conversion que C₂ si l’une des opérations suivantes contient :

  • E est une expression de collection et C₁ est une meilleure conversion de collection à partir d’une expression que C₂
  • E n'est pas une expression de collection et l'une des conditions suivantes est remplie :
    • E correspond exactement à T₁ et E ne correspond pas exactement aux T₂
    • E correspond exactement soit à la fois à T₁ et à T₂, soit ni à l'un ni à l'autre, et T₁ est une meilleure cible de conversion que T₂.
  • E est un groupe de méthodes, ...

Nous ajoutons une nouvelle définition pour une meilleure conversion de collection à partir d’une expression, comme suit :

Soit :

  • E est une expression de collection avec des expressions d’élément [EL₁, EL₂, ..., ELₙ]
  • T₁ et T₂ sont des types de collection
  • E₁ est le type d’élément de T₁
  • E₂ est le type d’élément de T₂
  • CE₁ᵢ sont les séries de conversions de ELᵢ vers E₁
  • CE₂ᵢ sont les séries de conversions de ELᵢ vers E₂

S’il existe une conversion d’identité à E₁partir de E₂ , les conversions d’éléments sont aussi bonnes que les autres. Dans le cas contraire, les conversions d’éléments sont E₁meilleures que les conversions d’éléments si E₂ :

  • Pour chaque ELᵢ, CE₁ᵢ est au moins aussi bon que CE₂ᵢ, et
  • Il y a au moins un i où CE₁ᵢ est meilleur que CE₂ᵢ Sinon, aucun ensemble de conversions d’éléments n’est meilleur que l’autre, et ils ne sont pas aussi bons que les autres.
    Les comparaisons de conversion sont effectuées à l’aide d’une meilleure conversion à partir d’une expression si ELᵢ ce n’est pas un élément de propagation. S’il ELᵢ s’agit d’un élément de propagation, nous utilisons une meilleure conversion du type d’élément de la collection de propagation vers E₁ ou E₂, respectivement.

C₁ est une meilleure conversion de collection à partir d’une expression que C₂ si :

  • Les deux T₁ et T₂ ne sont pas des types d’étendues, et T₁ sont implicitement convertibles en T₂, et T₂ n’est pas implicitement convertible en T₁, ou
  • E₁ n’a pas de conversion d’identité en E₂, et les conversions d’éléments à E₁ sont meilleures que les conversions d’éléments en E₂, ou
  • E₁ a une conversion d’identité en E₂, et l’une des conservations suivantes :
    • T₁ est System.ReadOnlySpan<E₁>, et T₂ est System.Span<E₂>, ou
    • T₁est System.ReadOnlySpan<E₁> ou System.Span<E₁>, et T₂ est un array_or_array_interface avec le type d’élément E₂

Sinon, aucun type de collection n’est préférable et le résultat est ambigu.

Remarque

Ces règles signifient que les méthodes qui exposent des surcharges qui prennent différents types d’éléments et sans conversion entre les types de collection sont ambiguës pour les expressions de collection vides. Par exemple :

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

M([]); // Ambiguous

Scenarios :

En anglais brut, les types de collection eux-mêmes doivent être identiques, ou sans ambiguïté (c’est-à-dire, List<T> et List<T> sont identiques, List<T> est clairement mieux que IEnumerable<T>, et List<T> ne peuvent pas être comparés), et HashSet<T> les conversions d’éléments pour le meilleur type de collection doivent également être identiques ou meilleures (c’est-à-dire, nous ne pouvons pas décider entre ReadOnlySpan<object> et Span<string> pour [""], l’utilisateur doit prendre cette décision). Voici d’autres exemples :

T₁ T₂ E C₁ Conversions C₂ Conversions Comparaison de CE₁ᵢ et de CE₂ᵢ Résultat
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ est mieux List<int> est choisi
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Sans objet T₂ n’est pas applicable List<int> est choisi
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Ni l’un ni l’autre n’est Ambigu
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ est mieux List<byte> est choisi
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Ni l’un ni l’autre n’est Ambigu
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ est mieux List<int?> est choisi
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ est mieux List<short> est choisi
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ est mieux IEnumerable<int> est choisi
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ est mieux List<byte> est choisi
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ est mieux int[] est choisi
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ est mieux ReadOnlySpan<string> est choisi
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Sans objet [Implicit Reference, Identity] T₁ n’est pas applicable ReadOnlySpan<object> est choisi
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ est mieux Span<string> est choisi
ReadOnlySpan<object> Span<string> [new object()] [Identity] Sans objet T₁ n’est pas applicable ReadOnlySpan<object> est choisi
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ est mieux ReadOnlySpan<InterpolatedStringHandler> est choisi
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Mais constante CE₂ᵢ est mieux ReadOnlySpan<string> est choisi
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ est mieux ReadOnlySpan<string> est choisi
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Sans objet [Interpolated String, Identity] T₁ n’est pas applicable ReadOnlySpan<FormattableString> est choisi
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ est mieux HashSet<short> est choisi
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ est mieux Span<short> est choisi

Questions ouvertes

Jusqu’à quel point devons-nous hiérarchiser ReadOnlySpan/Span les autres types ?

Comme indiqué aujourd’hui, les surcharges suivantes seraient ambiguës :

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

Jusqu’à quel point voulons-nous aller ici ? La List<T> variante semble raisonnable et sous-types d’existence List<T> aplentie. Mais la HashSet version a une sémantique très différente, comment sommes-nous sûrs que c’est réellement « pire » que ReadOnlySpan dans cette API ?