Compartir a través de


Mejor conversión del elemento de expresión de colección

Nota:

Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.

Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de idioma (LDM) pertinentes.

Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C# en el artículo sobre las especificaciones.

Problema planteado por el experto: https://github.com/dotnet/csharplang/issues/8374

Resumen

Actualizaciones de las mejores reglas de conversión para ser más coherentes con paramsy controlar mejor los escenarios de ambigüedad actuales. Por ejemplo, ReadOnlySpan<string> vs ReadOnlySpan<object> puede causar ambigüedades actualmente durante la resolución de sobrecarga para [""].

Diseño detallado

A continuación se muestran las mejores conversiones de las reglas de expresión. Reemplazan las reglas de https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Estas reglas son:

Dada una conversión implícita C₁ que convierte de una expresión E a un tipo T₁, y una conversión implícita C₂ que convierte de una expresión E a un tipo T₂, C₁ es una conversión mejor que C₂ si se cumple una de las siguientes condiciones:

  • E es una expresión de colección y C₁ es una mejor conversión de colección de la expresión que C₂
  • E no es una expresión de colección y una de las siguientes suspensiones:
    • E coincidencias T₁ exactas y E no coincide exactamente T₂
    • Ecoincide exactamente con o ninguno de T₁ y , y T₂ es un T₁de conversión mejor queT₂
  • E es un grupo de métodos, ...

Agregamos una nueva definición para una mejor conversión de colección de la expresión, como se indica a continuación:

Con estas premisas:

  • E es una expresión de colección con expresiones de elemento [EL₁, EL₂, ..., ELₙ]
  • T₁ y T₂ son tipos de colección
  • E₁ es el tipo de elemento de T₁
  • E₂ es el tipo de elemento de T₂
  • CE₁ᵢ son la serie de conversiones de ELᵢ a E₁
  • CE₂ᵢ son la serie de conversiones de ELᵢ a E₂

Si hay una conversión de identidad de E₁ a E₂, las conversiones de elementos son tan buenas entre sí. De lo contrario, las conversiones de elementos a E₁ son mejores que las conversiones de elementos a E₂ si:

  • Para cada ELᵢ, CE₁ᵢ es al menos tan bueno como CE₂ᵢ, y
  • Hay al menos un i donde CE₁ᵢ es mejor que CE₂ᵢ De lo contrario, ninguno de los conjuntos de conversiones de elementos es mejor que el otro, y tampoco son tan buenos como los unos a los otros.
    Las comparaciones de conversión se realizan mediante una mejor conversión de la expresión si ELᵢ no es un elemento de propagación. Si ELᵢ es un elemento de propagación, usamos una mejor conversión del tipo de elemento de la colección de propagación a E₁ o E₂, respectivamente.

C₁ es una mejor conversión de colección de la expresión que C₂ si:

  • Ambos T₁ y T₂ no son tipos de intervalo, y T₁ se convierte implícitamente en T₂y T₂ no se puede convertir implícitamente en T₁, o
  • E₁ no tiene una conversión de identidad a E₂y las conversiones de elementos a E₁ son mejores que las conversiones de elementos en E₂, o
  • E₁ tiene una conversión de identidad a E₂y una de las siguientes suspensiones:
    • T₁ es System.ReadOnlySpan<E₁>, y T₂ es System.Span<E₂>o
    • T₁es System.ReadOnlySpan<E₁> o System.Span<E₁>, y T₂ es un array_or_array_interface con tipo de elemento E₂

De lo contrario, ninguno de los tipos de colección es mejor y el resultado es ambiguo.

Nota:

Estas reglas significan que los métodos que exponen sobrecargas que toman tipos de elementos diferentes y sin una conversión entre los tipos de colección son ambiguos para las expresiones de colección vacías. Por ejemplo:

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

M([]); // Ambiguous

Escenarios:

En inglés sin formato, los propios tipos de colección deben ser iguales o no ambigüedades (es decir, List<T> y List<T> son iguales, List<T> es mejor que IEnumerable<T>y no se pueden comparar) y List<T>HashSet<T> las conversiones de elementos para el mejor tipo de colección también deben ser iguales o mejores (es decir, no podemos decidir entre ReadOnlySpan<object> y Span<string> para [""], el usuario debe tomar esa decisión). Más ejemplos de esto son:

T₁ T₂ E C₁ Conversiones C₂ Conversiones CE₁ᵢ frente a CE₂ᵢ Resultado
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ es mejor List<int> se selecciona
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] No aplicable T₂ no es aplicable List<int> se selecciona
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Tampoco es mejor Ambigua
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ es mejor List<byte> se selecciona
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Tampoco es mejor Ambigua
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ es mejor List<int?> se selecciona
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ es mejor List<short> se selecciona
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ es mejor IEnumerable<int> se selecciona
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ es mejor List<byte> se selecciona
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ es mejor int[] se selecciona
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ es mejor ReadOnlySpan<string> se selecciona
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] No aplicable [Implicit Reference, Identity] T₁ no es aplicable ReadOnlySpan<object> se selecciona
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ es mejor Span<string> se selecciona
ReadOnlySpan<object> Span<string> [new object()] [Identity] No aplicable T₁ no es aplicable ReadOnlySpan<object> se selecciona
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ es mejor ReadOnlySpan<InterpolatedStringHandler> se selecciona
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Pero constante CE₂ᵢ es mejor ReadOnlySpan<string> se selecciona
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ es mejor ReadOnlySpan<string> se selecciona
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] No aplicable [Interpolated String, Identity] T₁ no es aplicable ReadOnlySpan<FormattableString> se selecciona
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ es mejor HashSet<short> se selecciona
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ es mejor Span<short> se selecciona

Preguntas abiertas

¿Hasta dónde deberíamos priorizar ReadOnlySpan/Span otros tipos?

Como se especifica hoy, las siguientes sobrecargas serían ambiguas:

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

¿Hasta qué punto queremos ir aquí? La List<T> variante parece razonable y los subtipos de List<T> existen aplenty. Pero la HashSet versión tiene una semántica muy diferente, ¿cómo estamos seguros de que es realmente "peor" que ReadOnlySpan en esta API?