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


params Collections

Заметка

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

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

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

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

Сводка

На языке C# 12 добавлена поддержка создания экземпляров типов коллекций за пределами только массивов. См. выражения коллекции . Это предложение расширяет поддержку params на все такие типы коллекций.

Мотивация

Параметр массива params предоставляет удобный способ вызова метода, который принимает произвольный список аргументов длины. Сегодня params параметр должен быть типом массива. Тем не менее, это может быть полезно для разработчика, чтобы иметь то же удобство при вызове API, которые принимают другие типы коллекций. Например, ImmutableArray<T>, ReadOnlySpan<T>или обычный IEnumerable. Особенно в случаях, когда компилятор может избежать неявного выделения массива для создания коллекции (ImmutableArray<T>, ReadOnlySpan<T>и т. д.).

Сегодня, в случаях, когда API принимает тип коллекции, разработчики обычно добавляют перегрузку params, которая принимает массив, создает нужную коллекцию и вызывает исходную перегрузку с этой коллекцией. Таким образом, потребители API вынуждены жертвовать дополнительным выделением массива в пользу удобства.

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

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

Параметры метода

Раздел параметров метода настраивается следующим образом.

formal_parameter_list
    : fixed_parameters
-    | fixed_parameters ',' parameter_array
+    | fixed_parameters ',' parameter_collection
-    | parameter_array
+    | parameter_collection
    ;

-parameter_array
+parameter_collection
-    : attributes? 'params' array_type identifier
+    : attributes? 'params' 'scoped'? type identifier
    ;

parameter_collection состоит из необязательного набора атрибутов , модификатора params, необязательного модификатора scoped, типа и идентификатора . Коллекция параметров объявляет один параметр заданного типа с заданным именем. Тип коллекции параметров должен быть одним из следующих допустимых типов целевых объектов для выражения коллекции (см. https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversions):

  • Одномерный массив типа T[], в таком случае элемент типа T.
  • Тип диапазона
    • System.Span<T>
    • System.ReadOnlySpan<T>
      В каких случаях элемент типа T
  • Тип с соответствующим методом создания, который можно вызвать без дополнительных аргументов, который по крайней мере так же доступен, как декларирующий элемент, и с соответствующим типом элемента, результатом которого является это определение.
  • типа структуры или класса, реализующего System.Collections.IEnumerable где:
    • Тип имеет конструктор, который можно вызвать без аргументов, и конструктор по крайней мере доступен как декларативный элемент.

    • Тип имеет метод экземпляра (а не расширения) Add где:

      • Метод можно вызвать с одним аргументом значения.
      • Если метод является универсальным, аргументы типа можно вывести из аргумента.
      • Метод как минимум доступен так же, как объявляющий член.

      В этом случае тип элемента является типом итерации типа .

  • Тип интерфейса
    • System.Collections.Generic.IEnumerable<T>,
    • System.Collections.Generic.IReadOnlyCollection<T>,
    • System.Collections.Generic.IReadOnlyList<T>,
    • System.Collections.Generic.ICollection<T>,
    • System.Collections.Generic.IList<T>
      В каких случаях элемент типа T

В вызове метода коллекция параметров разрешает указывать один аргумент заданного типа параметра, или позволяет указывать ноль или больше аргументов типа элемента коллекции . Коллекции параметров описаны далее в коллекциях параметров .

parameter_collection может возникать после необязательного параметра, но не может иметь значение по умолчанию — пропуск аргументов для parameter_collection вместо этого приведет к созданию пустой коллекции.

Коллекции параметров

Раздел массивов параметров переименован и скорректирован следующим образом.

Параметр, объявленный модификатором params, является коллекцией параметров. Если формальный список параметров включает коллекцию параметров, он должен быть последним в списке, и он должен быть типом, указанным в параметрах метода разделе.

Примечание. Невозможно объединить модификатор params с модификаторами in, outили ref. конечная сноска

Коллекция параметров позволяет указывать аргументы одним из двух способов в вызове метода:

  • Аргумент, заданный для коллекции параметров, может быть одним выражением, которое неявно преобразуется в тип коллекции параметров. В этом случае коллекция параметров действует точно так же, как и параметр значения.
  • Кроме того, вызов может указывать ноль или больше аргументов для коллекции параметров, где каждый аргумент является выражением, неявно преобразованным в тип элемента коллекции параметров . В этом случае вызов создает экземпляр типа коллекции параметров в соответствии с правилами, указанными в выражениях коллекции , как если бы аргументы использовались в качестве элементов выражения в выражении коллекции в том же порядке, и использует созданный экземпляр коллекции в качестве фактического аргумента. Исходные неконвертированные аргументы и используются при создании экземпляра коллекции.

За исключением разрешения переменного числа аргументов в вызове, коллекция параметров точно эквивалентна параметру значения одного типа.

При разрешении перегрузки метод с коллекцией параметров может применяться либо в обычной форме, либо в развернутой форме. Расширенная форма метода доступна только в том случае, если обычная форма метода неприменима, и только если применимый метод с той же сигнатурой, что и развернутая форма, еще не объявлена в том же типе.

Потенциальная неоднозначность возникает между обычной формой и расширенной формой метода с одним аргументом коллекции параметров, когда его можно использовать в качестве самой коллекции параметров и в качестве элемента коллекции параметров одновременно. Неоднозначность не представляет никакой проблемы, так как она может быть решена путем вставки приведения или использования выражения коллекции при необходимости.

Сигнатуры и перегрузка

Все правила касательно модификатора params в сигнатурах и перегрузке остаются без изменений.

Применимый член функции

Раздел Применимый член функции корректируется следующим образом.

Если член функции, содержащий коллекцию параметров, неприменим в обычной форме, член функции может быть применим в этом развернутой форме:

  • Если коллекция параметров не является массивом, развернутая форма не применима для языковых версий C# 12 и ниже.
  • Расширенная форма создается путем замены коллекции параметров в объявлении члена функции нулевыми или более параметрами значения типа элемента коллекции параметров таким образом, чтобы число аргументов в списке аргументов A совпадает с общим числом параметров. Если A имеет меньше аргументов, чем число фиксированных параметров в объявлении члена функции, расширенная форма члена функции не может быть создана и поэтому неприменимо.
  • В противном случае развернутая форма применима, если для каждого аргумента в Aодно из следующих значений имеет значение true:
    • режим передачи параметров аргумента идентичен режиму передачи параметров соответствующего параметра и
      • для параметра фиксированного значения или параметра значения, созданного расширением, неявное преобразование существует из выражения аргумента в тип соответствующего параметра или
      • для параметра in, outили ref тип выражения аргумента идентичен типу соответствующего параметра.
    • режим передачи параметров аргумента является значением, а режим передачи параметров соответствующего параметра является входным, и неявное преобразование существует из выражения аргумента в тип соответствующего параметра.

Более подходящий элемент функции

Раздел "Улучшенный элемент функции" изменяется следующим образом.

Учитывая список аргументов A с набором выражений аргументов {E₁, E₂, ..., Eᵥ} и двумя применимыми элементами функции Mᵥ и Mₓ с типами параметров {P₁, P₂, ..., Pᵥ} и {Q₁, Q₂, ..., Qᵥ}, Mᵥ определяется как более подходящий элемент функции, чем Mₓ, если

  • для каждого аргумента неявное преобразование из Eᵥ в Qᵥ не лучше неявного преобразования из Eᵥ в Pᵥи
  • для хотя бы одного аргумента преобразование из Eᵥ в Pᵥ лучше, чем преобразование из Eᵥ в Qᵥ.

Если последовательности типов параметров {P₁, P₂, ..., Pᵥ} и {Q₁, Q₂, ..., Qᵥ} эквивалентны (т. е. каждый Pᵢ имеет тождественное преобразование в соответствующую Qᵢ), то для определения лучшего элемента функции применяются следующие правила в порядке разрешения конфликтов.

  • Если Mᵢ является не универсальным методом и Mₑ является универсальным методом, то Mᵢ лучше, чем Mₑ.
  • В противном случае, если Mᵢ применимо в обычной форме и Mₑ имеет коллекцию парамс и применимо только в расширенной форме, то Mᵢ лучше, чем Mₑ.
  • В противном случае, если оба метода содержат коллекции параметров и применимы только в их развернутых формах, и если коллекция параметров Mᵢ имеет меньше элементов, чем коллекция параметров Mₑ, то Mᵢ лучше, чем Mₑ.
  • В противном случае, если Mᵥ имеет более конкретные типы параметров, чем Mₓ, Mᵥ лучше, чем Mₓ. Позвольте {R1, R2, ..., Rn} и {S1, S2, ..., Sn} представлять неинициализированные и нераскрытые типы параметров Mᵥ и Mₓ. Mᵥтипы параметров более конкретны, чем Mₓ, если для каждого параметра Rx не менее конкретен, чем Sx, и для по крайней мере одного параметра Rx более конкретен, чем Sx:
    • Параметр типа менее специфичен, чем параметр без типа.
    • Рекурсивно созданный тип более конкретный, чем другой созданный тип (с одинаковым числом аргументов типа), если по крайней мере один аргумент типа более конкретный, а аргумент типа не является менее конкретным, чем соответствующий аргумент типа в другом.
    • Тип массива более конкретный, чем другой тип массива (с тем же числом измерений), если тип элемента первого является более конкретным, чем тип элемента второго.
  • В противном случае, если один из участников является непродвинутым оператором, а другой — продвинутым оператором, то непродвинутый оператор является более предпочтительным.
  • Если ни один член функции не был найден лучше, и все параметры Mᵥ имеют соответствующий аргумент, в то время как аргументы по умолчанию должны быть заменены по крайней мере одним необязательным параметром в Mₓ, то Mᵥ лучше, чем Mₓ.
  • Если для хотя бы одного параметра Mᵥ используется более подходящий вариант передачи параметров (§12.6.4.4), чем для соответствующего параметра в Mₓ, и ни для одного из параметров в Mₓ не используется более подходящий вариант передачи параметров, чем Mᵥ, то Mᵥ будет лучше, чем Mₓ.
  • В противном случае, если оба метода имеют коллекции params и применимы только в развернутых формах, Mᵢ лучше, чем Mₑ, если один и тот же набор аргументов соответствует элементам коллекции для обоих методов, и выполняется одно из следующих условий (это соответствует https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.md):
    • обе коллекции параметров не являются типом span, а неявное преобразование существует из коллекции параметров Mᵢ в коллекцию параметров Mₑ
    • коллекция параметров Mᵢ — это System.ReadOnlySpan<Eᵢ>, а коллекция параметров Mₑ — это System.Span<Eₑ>, и тождественное преобразование существует от Eᵢ до Eₑ
    • коллекция MᵢSystem.ReadOnlySpan<Eᵢ> или System.Span<Eᵢ>, а коллекция Mₑ params — это array_or_array_interface__typeEₑ, а преобразование удостоверений существует от Eᵢ до Eₑ
  • В противном случае, ни один член функции не является лучше.

Причина, по которой новое правило игры на перенос помещено в конец списка, заключается в последнем пункте.

  • обе коллекции параметров не являются типом span, а неявное преобразование существует из коллекции параметров Mᵢ в коллекцию параметров Mₑ

Это применимо к массивам и, следовательно, выполнение разрыва связи ранее приведет к изменению поведения для существующих сценариев.

Например:

class Program
{
    static void Main()
    {
        Test(1);
    }

    static void Test(in int x, params C2[] y) {} // There is an implicit conversion from `C2[]` to `C1[]`
    static void Test(int x, params C1[] y) {} // Better candidate because of "better parameter-passing choice"
}

class C1 {}
class C2 : C1 {}

Если применяются какие-либо из предыдущих правил разрешения связей (включая правило "наилучшие преобразования аргументов"), результат разрешения перегрузки может отличаться по сравнению с случаем, когда явное выражение коллекции используется в качестве аргумента.

Например:

class Program
{
    static void Test1()
    {
        M1(['1', '2', '3']); // IEnumerable<char> overload is used because `char` is an exact match
        M1('1', '2', '3');   // IEnumerable<char> overload is used because `char` is an exact match
    }

    static void M1(params IEnumerable<char> value) {}
    static void M1(params System.ReadOnlySpan<MyChar> value) {}

    class MyChar
    {
        private readonly int _i;
        public MyChar(int i) { _i = i; }
        public static implicit operator MyChar(int i) => new MyChar(i);
        public static implicit operator char(MyChar c) => (char)c._i;
    }

    static void Test2()
    {
        M2([1]); // Span overload is used
        M2(1);   // Array overload is used, not generic
    }

    static void M2<T>(params System.Span<T> y){}
    static void M2(params int[] y){}

    static void Test3()
    {
        M3("3", ["4"]); // Ambiguity, better-ness of argument conversions goes in opposite directions.
        M3("3", "4");   // Ambiguity, better-ness of argument conversions goes in opposite directions.
                        // Since parameter types are different ("object, string" vs. "string, object"), tie-breaking rules do not apply
    }

    static void M3(object x, params string[] y) {}
    static void M3(string x, params Span<object> y) {}
}

Однако основной проблемой являются сценарии, в которых перегрузки отличаются только по типу коллекции params, но типы коллекций имеют один и тот же тип элемента. Поведение должно соответствовать явным выражениям коллекции в этих случаях.

Условие ", если одинаковый набор аргументов соответствует элементам коллекции для обоих методов" важно для таких сценариев:

class Program
{
    static void Main()
    {
        Test(x: 1, y: 2); // Ambiguous
    }

    static void Test(int x, params System.ReadOnlySpan<int> y) {}
    static void Test(int y, params System.Span<int> x) {}
}

Не имеет смысла сравнивать коллекции, созданные из разных элементов.

Этот раздел был рассмотрен на LDM и утвержден.

Одним из эффектов этих правил является то, что при раскрытии params различных типов элементов они будут неоднозначными при вызове с пустым списком аргументов. Например:

class Program
{
    static void Main()
    {
        // Old scenarios
        C.M1(); // Ambiguous since params arrays were introduced
        C.M1([]); // Ambiguous since params arrays were introduced

        // New scenarios
        C.M2(); // Ambiguous in C# 13
        C.M2([]); // Ambiguous in C# 13
        C.M3(); // Ambiguous in C# 13
        C.M3([]); // Ambiguous in C# 13
    }

    public static void M1(params int[] a) {
    }
    
    public static void M1(params int?[] a) {
    }
    
    public static void M2(params ReadOnlySpan<int> a) {
    }
    
    public static void M2(params Span<int?> a) {
    }
    
    public static void M3(params ReadOnlySpan<int> a) {
    }
    
    public static void M3(params ReadOnlySpan<int?> a) {
    }
}

Учитывая, что мы отдаем приоритет типу элемента над всем остальным, это кажется разумным; нет никаких указаний для языка, предпочел бы пользователь int? вместо int в этом сценарии.

Динамическая привязка

Развернутые формы кандидатов, использующих коллекции параметров, отличные от массивов, не будут рассматриваться как допустимые кандидаты текущим привязчиком среды выполнения C#.

Если primary_expression не имеет типа времени компиляции dynamic, вызов метода подвергается ограниченной проверке во время компиляции, как описано в §12.6.5 Проверка вызова динамического члена.

Если только один кандидат проходит тест, вызов кандидата статически привязан при выполнении всех следующих условий:

  • Кандидат — это локальная функция
  • кандидат либо не является универсальным, либо его аргументы типа явно указаны.
  • Нет неоднозначности между нормальными и расширенными формами кандидата, которую не удастся разрешить на этапе компиляции.

В противном случае invocation_expression динамически привязан.

Если только один кандидат прошел указанный выше тест:

  • Если этот кандидат является локальной функцией, возникает ошибка во время компиляции;
  • Если этот кандидат применим только в развернутой форме с использованием коллекций параметров, не являющихся массивами, это приводит к ошибке компиляции.

Мы также должны рассмотреть вопрос о возврате и исправлении нарушений спецификаций, влияющих на локальные функции сегодня, см. https://github.com/dotnet/roslyn/issues/71399.

LDM подтвердил, что мы хотим исправить это нарушение спецификации.

Деревья выражений

Выражения коллекции не поддерживаются в деревах выражений. Аналогичным образом, развернутые формы коллекций параметров, отличных от массива, также не будут поддерживаться в деревьях выражений. Мы не будем изменять способ привязки лямбда-выражений для деревьев выражений, с целью избежать использования API, использующих расширенные формы коллекций параметров, которые не являются массивами.

Порядок оценки с коллекциями без массивов в нетривиальных сценариях

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

Именованные аргументы

Экземпляр коллекции создается и заполняется после оценки лексически предыдущего аргумента, но до оценки лексически следующего аргумента.

Например:

class Program
{
    static void Main()
    {
        Test(b: GetB(), c: GetC(), a: GetA());
    }

    static void Test(int a, int b, params MyCollection c) {}

    static int GetA() => 0;
    static int GetB() => 0;
    static int GetC() => 0;
}

Порядок оценки приведен ниже.

  1. называется GetB
  2. MyCollection создается и заполняется, GetC вызывается в процессе.
  3. вызывается GetA
  4. вызывается Test

Обратите внимание, что в случае массива params массив создается прямо перед вызовом целевого метода, после того как все аргументы оцениваются в их лексическом порядке.

Комбинированное присваивание

Экземпляр коллекции создается и заполняется после оценки лексически предыдущего индекса, но до оценки лексически следующего индекса. Экземпляр используется для вызова геттера и сеттера целевого индексатора.

Например:

class Program
{
    static void Test(Program p)
    {
        p[GetA(), GetC()]++;
    }

    int this[int a, params MyCollection c] { get => 0; set {} }

    static int GetA() => 0;
    static int GetC() => 0;
}

Порядок оценки приведен ниже.

  1. GetA вызывается и кэшируется
  2. MyCollection создается, заполняется и кэшируется, GetC вызывается в процессе.
  3. Метод получения индексатора вызывается с закэшированными значениями для индексов
  4. Результат инкрементируется
  5. Метод установки индексатора вызывается с сохраненными в кэше значениями индексов и итоговым результатом инкремента.

Пример с пустой коллекцией:

class Program
{
    static void Test(Program p)
    {
        p[GetA()]++;
    }

    int this[int a, params MyCollection c] { get => 0; set {} }

    static int GetA() => 0;
}

Порядок оценки приведен ниже.

  1. GetA вызывается и кэшируется
  2. Был создан и кэширован пустой MyCollection
  3. Метод геттера индексатора вызывается с кэшированными значениями для индексов
  4. Результат инкрементируется
  5. Метод установки индексатора вызывается с сохраненными в кэше значениями индексов и итоговым результатом инкремента.

Инициализатор объектов

Экземпляр коллекции создается и заполняется после оценки лексически предыдущего индекса, но до оценки лексически следующего индекса. Экземпляр используется для вызова геттера индексатора столько раз, сколько потребуется.

Например:

class C1
{
    public int F1;
    public int F2;
}

class Program
{
    static void Test()
    {
        _ = new Program() { [GetA(), GetC()] = { F1 = GetF1(), F2 = GetF2() } };
    }

    C1 this[int a, params MyCollection c] => new C1();

    static int GetA() => 0;
    static int GetC() => 0;
    static int GetF1() => 0;
    static int GetF2() => 0;
}

Порядок оценки приведен ниже.

  1. GetA вызывается и кэшируется
  2. MyCollection создается, заполняется и кэшируется, GetC вызывается в процессе.
  3. Геттер индексатора вызывается с кешированными значениями индексов
  4. GetF1 вычисляется и присваивается полю F1 из C1, возвращенного на предыдущем шаге.
  5. Метод получения индексатора вызывается с кэшируемыми значениями для индексов
  6. GetF2 вычисляется и присваивается полю F2 из C1, возвращенного на предыдущем шаге.

Обратите внимание, что в случае массива params его элементы оцениваются и кэшируются, но для каждого вызова геттера индексатора используется новый экземпляр массива с теми же значениями внутри. В приведенном выше примере порядок оценки приведен ниже.

  1. GetA вызывается и кэшируется
  2. GetC вызывается и кэшируется
  3. Метод получения индексатора вызывается с кэшированным GetA и новым массивом, заполненным кэшированным GetC.
  4. GetF1 вычисляется и присваивается полю F1 из C1, возвращенного на предыдущем шаге.
  5. Метод получения индексатора вызывается с кэшированным GetA и новым массивом, заполненным кэшированным GetC.
  6. GetF2 вычисляется и присваивается полю F2 из C1, возвращенного на предыдущем шаге.

Пример с пустой коллекцией:

class C1
{
    public int F1;
    public int F2;
}

class Program
{
    static void Test()
    {
        _ = new Program() { [GetA()] = { F1 = GetF1(), F2 = GetF2() } };
    }

    C1 this[int a, params MyCollection c] => new C1();

    static int GetA() => 0;
    static int GetF1() => 0;
    static int GetF2() => 0;
}

Порядок оценки приведен ниже.

  1. GetA вызывается и кэшируется
  2. Создается и кэшируется пустая MyCollection
  3. Геттер индексатора вызывается с закэшированными значениями для индексов
  4. GetF1 вычисляется и присваивается полю F1 из C1, возвращенного на предыдущем шаге.
  5. Метод получения индексатора вызывается с кэшируемыми значениями для индексов
  6. GetF2 вычисляется и присваивается полю F2 из C1, возвращенного на предыдущем шаге.

Безопасность ссылок

Раздел, касающийся выражений коллекций, применим при создании коллекций параметров, когда API вызываются в расширенном формате.

Параметры params неявно scoped, если их тип является структурой с модификатором ref. Для переопределения можно использовать UnscopedRefAttribute.

Метаданные

В метаданных мы могли бы помечать параметры params, которые не являются массивами, с помощью System.ParamArrayAttribute, как сейчас отмечены массивы params. Тем не менее, похоже, что мы будем гораздо безопаснее использовать другой атрибут для параметров, отличных от массива params. Например, текущий компилятор VB не сможет использовать их, оформленных с помощью ParamArrayAttribute, ни в стандартной форме, ни в расширенной. Таким образом, добавление модификатора params, вероятно приведет к сбоям у пользователей VB и, очень вероятно, у пользователей, работающих с другими языками или инструментами.

Учитывая, что параметры, отличные от массива params, помечены новым System.Runtime.CompilerServices.ParamCollectionAttribute.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)]
    public sealed class ParamCollectionAttribute : Attribute
    {
        public ParamCollectionAttribute() { }
    }
}

Этот раздел был рассмотрен на LDM и утвержден.

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

Выделение стека

Вот цитата из https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#unresolved-questions: "Выделение памяти в стеке для огромных коллекций может привести к переполнению стека. Должен ли компилятор использовать эвристику для размещения этих данных в куче? Должен ли язык не быть указан, чтобы обеспечить эту гибкость? Мы должны следовать спецификации для params Span<T>. Похоже, нам нужно ответить на вопросы в контексте этого предложения.

[Решено] Неявные scoped параметры

Существует предложение о том, что, когда params изменяет параметр ref struct, его следует рассматривать как объявленный scoped. Аргумент делается в том числе случаев, когда требуется, чтобы параметр был ограничен практически 100% при просмотре вариантов BCL. В некоторых случаях, которые нуждаются в этом, значение по умолчанию можно перезаписать с помощью [UnscopedRef].

Однако может быть нежелательно менять значение по умолчанию только из-за наличия модификатора params. Обычно, в случаях переопределения или реализации сценариев модификатор params может не совпадать.

Резолюция:

Параметры params неявно ограничены — https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-11-15.md#params-improvements.

[Решено] Рассмотрите возможность применения scoped или params для переопределений

Ранее мы заявили, что параметры params должны быть scoped по умолчанию. Однако это представляет нечетное поведение при переопределении, из-за существующих правил вокруг переопределения params:

class Base
{
    internal virtual Span<int> M1(scoped Span<int> s1, params Span<int> s2) => throw null!;
}

class Derived : Base
{
    internal override Span<int> M1(Span<int> s1, // Error, missing `scoped` on override
                                   Span<int> s2  // Proposal: Error: parameter must include either `params` or `scoped`
                                  ) => throw null!;
}

Мы имеем разницу в поведении между переносом params и переносом scoped через переопределения здесь: params наследуется неявно, включая scoped, тогда как scoped само по себе не наследуется неявно и должно повторяться на каждом уровне.

предложение. Мы должны обеспечить, чтобы переопределения параметров params явно указывали params или scoped, если исходное определение является параметром scoped. Другими словами, s2 в Derived должны иметь params, scopedили обоих.

Резолюция:

Для переопределения параметра scoped необходимо явно указать params или params в случае, если потребуется параметр, отличный отparams - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#params-and-scoped-across-overrides.

[Решено] Должно ли наличие обязательных членов предотвратить объявление параметра params?

Рассмотрим следующий пример:

using System.Collections;
using System.Collections.Generic;

public class MyCollection1 : IEnumerable<long>
{
    IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
    public void Add(long l) => throw null;

    public required int F; // Collection has required member and constructor doesn't initialize it explicitly
}

class Program
{
    static void Main()
    {
        Test(2, 3); // error CS9035: Required member 'MyCollection1.F' must be set in the object initializer or attribute constructor.
    }

    // Proposal: An error is reported for the parameter indicating that the constructor that is required
    // to be available doesn't initialize required members. In other words, one is able
    // to declare such a parameter under the specified conditions.
    static void Test(params MyCollection1 a)
    {
    }
}

Резолюция:

Мы проверим required членов по отношению к конструктору, который используется для определения соответствия требованиям для параметра params в месте объявления - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters.

Альтернативы

Существует альтернативное предложение , которое расширяет params только для ReadOnlySpan<T>.

Кроме того, можно сказать, что с выражениями коллекции в языке теперь отпадает необходимость расширять поддержку params. Для любого типа коллекции. Чтобы использовать API с типом коллекции, разработчику просто нужно добавить два символа, [ перед развернутым списком аргументов и ] после него. Учитывая, что расширение поддержки params может быть излишним, особенно учитывая, что другие языки вряд ли будут поддерживать использование params параметров, отличных от массива, в ближайшее время.