Поделиться через


Лучшее преобразование элементов выражения коллекции

Заметка

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.

Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия фиксируются в соответствующих заметках собраний по разработке языка (LDM) .

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Проблема чемпиона: https://github.com/dotnet/csharplang/issues/8374

Сводка

Обновляются правила преобразования, которые должны быть более согласованными с paramsи более эффективно обрабатывать текущие сценарии неоднозначности. Например, ReadOnlySpan<string> и ReadOnlySpan<object> в настоящее время могут вызвать неоднозначность во время разрешения перегрузки для [""].

Подробный дизайн

Ниже приведено лучшее преобразование из правил выражений. Эти правила заменяют правила в https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#overload-resolution.

Ниже приведены следующие правила:

Учитывая неявное преобразование C₁, которое преобразуется из выражения E в тип T₁, и неявное преобразование C₂, которое преобразуется из выражения E в тип T₂, C₁ — это лучшее преобразование, чем C₂, если выполняется одно из следующих условий:

  • — это выражение коллекции , а — это лучшее преобразование коллекции из выражения, чем .
  • E не является выражением коллекции , и выполняется одно из следующих условий:
    • E точно совпадает с T₁, а E не полностью соответствует T₂
    • E точно соответствует либо обоим T₁ и T₂, либо ни тому, ни другому, а T₁ — это лучшее целевое преобразование по сравнению с T₂.
  • E — это группа методов, ...

Мы добавляем новое определение для для более эффективного преобразования коллекции из выраженияследующим образом:

Данный:

  • E — это выражение коллекции с выражениями элементов [EL₁, EL₂, ..., ELₙ]
  • T₁ и T₂ являются типами коллекций
  • E₁ — это тип элемента T₁
  • E₂ — это тип элемента T₂
  • CE₁ᵢ — это ряд преобразований из ELᵢ в E₁
  • CE₂ᵢ — это ряд преобразований из ELᵢ в E₂

Если существует тождественное преобразование из E₁ в E₂, то преобразования элементов так же хороши друг, как и друг. В противном случае преобразования элементов в E₁лучше, чем преобразования элементов в E₂, если:

  • Для каждого ELᵢCE₁ᵢ не менее хорош, чем CE₂ᵢ, и
  • Существует по крайней мере один i, где CE₁ᵢ лучше, чем CE₂ᵢ. В противном случае ни один набор преобразований элементов не лучше другого, и они также не такие хорошие, как друг друга.
    Сравнения преобразований выполняются с помощью более эффективного преобразования из выражения, если ELᵢ не является элементом распространения. Если ELᵢ является элементом распространения, мы используем лучшее преобразование из типа элемента коллекции распространения в E₁ или E₂соответственно.

C₁ — это лучшее преобразование коллекции из выражения, чем C₂, если:

  • Оба T₁ и T₂ не типы диапазонов, и T₁ неявно преобразуется в T₂, и T₂ неявно преобразуется в T₁или
  • E₁ не имеет преобразования идентичности в E₂, а преобразования элементов в E₁лучше, чем преобразования элементов в E₂, или же...
  • E₁ имеет тождественное преобразование в E₂и выполняется одно из следующих условий.
    • T₁ это System.ReadOnlySpan<E₁>, и T₂ это System.Span<E₂>, или
    • T₁ System.ReadOnlySpan<E₁> или System.Span<E₁>, T₂ — это array_or_array_interface с типом элементаE₂

В противном случае ни один тип коллекции не лучше, и результат неоднозначный.

Заметка

Эти правила означают, что методы, предоставляющие перегрузки, работающие с разными типами элементов без преобразования между типами коллекций, являются неоднозначными для пустых выражений для коллекций. В качестве примера:

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

M([]); // Ambiguous

Сценарии:

На обычном английском языке сами типы коллекций должны быть одинаковыми. или однозначно лучше (т. е. List<T> и List<T> совпадают, List<T> однозначно лучше, чем IEnumerable<T>, а List<T> и HashSet<T> нельзя сравнить), а преобразования элементов для лучшего типа коллекции также должны быть одинаковыми или более лучшими (т. е. мы не можем решить между ReadOnlySpan<object> и Span<string> для [""], пользователь должен принять это решение). Ниже приведены дополнительные примеры:

T₁ T₂ E C₁ Преобразования C₂ Преобразования CE₁ᵢ и CE₂ᵢ Результат
List<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ лучше List<int> выбран
List<int> List<byte> [(int)1, (byte)2] [Identity, Implicit Numeric] Неприменимо T₂ неприменимо List<int> выбран
List<int> List<byte> [1, (byte)2] [Identity, Implicit Numeric] [Implicit Constant, Identity] Ни один не лучше Двусмысленный
List<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ лучше List<byte> выбран
List<int?> List<long> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] Ни один не лучше Двусмысленный
List<int?> List<ulong> [1, 2, 3] [Implicit Nullable, Implicit Nullable, Implicit Nullable] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ лучше List<int?> выбран
List<short> List<long> [1, 2, 3] [Implicit Numeric, Implicit Numeric, Implicit Numeric] [Implicit Numeric, Implicit Numeric, Implicit Numeric] CE₁ᵢ лучше List<short> выбран
IEnumerable<int> List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ лучше IEnumerable<int> выбран
IEnumerable<int> List<byte> [(byte)1, (byte)2] [Implicit Numeric, Implicit Numeric] [Identity, Identity] CE₂ᵢ лучше List<byte> выбран
int[] List<byte> [1, 2, 3] [Identity, Identity, Identity] [Implicit Constant, Implicit Constant, Implicit Constant] CE₁ᵢ лучше int[] выбран
ReadOnlySpan<string> ReadOnlySpan<object> ["", "", ""] [Identity, Identity, Identity] [Implicit Reference, Implicit Reference, Implicit Reference] CE₁ᵢ лучше ReadOnlySpan<string> выбран
ReadOnlySpan<string> ReadOnlySpan<object> ["", new object()] Неприменимо [Implicit Reference, Identity] T₁ неприменимо ReadOnlySpan<object> выбран
ReadOnlySpan<object> Span<string> ["", ""] [Implicit Reference] [Identity] CE₂ᵢ лучше Span<string> выбран
ReadOnlySpan<object> Span<string> [new object()] [Identity] Неприменимо T₁ неприменимо ReadOnlySpan<object> выбран
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{1}"] [Interpolated String Handler] [Identity] CE₁ᵢ лучше ReadOnlySpan<InterpolatedStringHandler> выбран
ReadOnlySpan<InterpolatedStringHandler> ReadOnlySpan<string> [$"{"blah"}"] [Interpolated String Handler] [Identity] - Но константа CE₂ᵢ лучше ReadOnlySpan<string> выбран
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}"] [Identity] [Interpolated String] CE₂ᵢ лучше ReadOnlySpan<string> выбран
ReadOnlySpan<string> ReadOnlySpan<FormattableString> [$"{1}", (FormattableString)null] Неприменимо [Interpolated String, Identity] T₁ неприменимо ReadOnlySpan<FormattableString> выбран
HashSet<short> Span<long> [1, 2] [Implicit Constant, Implicit Constant] [Implicit Numeric, Implicit Numeric] CE₁ᵢ лучше HashSet<short> выбран
HashSet<long> Span<short> [1, 2] [Implicit Numeric, Implicit Numeric] [Implicit Constant, Implicit Constant] CE₂ᵢ лучше Span<short> выбран

Открытые вопросы

В какой степени мы должны расставлять приоритеты ReadOnlySpan/Span над другими типами?

Как указано сегодня, следующие перегрузки будут неоднозначными:

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

Насколько далеко мы хотим пойти сюда? Вариант List<T> кажется разумным, и подтипов List<T> существует много. Но версия HashSet имеет очень другую семантику, насколько мы уверены, что это на самом деле "хуже", чем ReadOnlySpan в этом API?