次の方法で共有


params Collections

手記

この記事は機能仕様です。 仕様は、機能の設計ドキュメントとして機能します。 これには、提案された仕様の変更と、機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が最終決定され、現在の ECMA 仕様に組み込まれるまで公開されます。

機能の仕様と完成した実装の間には、いくつかの違いがある可能性があります。 これらの違いは、関連する 言語設計会議 (LDM) ノートでキャプチャされます。

機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。

概要

C# 12 言語では、配列以外のコレクション型のインスタンスを作成するためのサポートが追加されました。 コレクション式を参照してください。 この提案は、すべてのコレクション型に対してparamsのサポートを拡張します。

モチベーション

params 配列パラメーターは、任意の長さの引数リストを受け取るメソッドを呼び出すのに便利な方法です。 現在 params パラメーターは配列型である必要があります。 ただし、他のコレクション型を受け取る API を呼び出すときに、開発者が同じ利便性を持つことが有益な場合があります。 たとえば、ImmutableArray<T>ReadOnlySpan<T>、プレーン IEnumerableなどです。 特に、コンパイラがコレクション (ImmutableArray<T>ReadOnlySpan<T>など) を作成する目的で暗黙的な配列の割り当てを回避できる場合。

現在、API がコレクション型を受け取る状況では、開発者は通常、配列を受け取り、ターゲット コレクションを構築し、そのコレクションで元のオーバーロードを呼び出す params オーバーロードを追加するため、API のコンシューマーは便宜上、余分な配列割り当てを交換する必要があります。

もう 1 つの動機は、既存のソース コードを再コンパイルするだけで、パラメータースパンオーバーロードを追加し、配列バージョンよりも優先される機能です。

詳細な設計

メソッド パラメーター

メソッドのパラメーター セクションは、次のように調整されます。

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 修飾子、、および 識別子で構成されます。 パラメーター コレクションは、指定された名前を持つ特定の型の 1 つのパラメーターを宣言します。 パラメーター コレクションの は、コレクション式の次の有効なターゲット型のいずれかになります (https://github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/collection-expressions.md#conversionsを参照)。

  • 1 次元 配列型T[]。この場合、要素型T
  • span 型
    • System.Span<T>
    • System.ReadOnlySpan<T>
      どのような場合に要素の型Tであるか
  • 生成メソッドに最適なであり、宣言しているメンバーと同等以上のアクセス可能性を有し、その判断から得た対応する 要素型
  • 次を実装するstruct 型 または class 型 System.Collections.IEnumerable
    • には、引数なしで呼び出すことができるコンストラクターがあり、コンストラクターは少なくとも宣言メンバーと同じくらいアクセスできます。

    • には、インスタンス (拡張ではない) メソッド Add があります。

      • メソッドは、単一の値引数を使用して呼び出すことができます。
      • メソッドがジェネリックの場合は、引数から型引数を推論できます。
      • このメソッドは、少なくとも宣言メンバーと同じくらいアクセス可能です。

      その場合、要素型 は、型の反復型 です。

  • interface 型
    • 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>
      この場合、element 型T

メソッドの呼び出しでは、パラメーター コレクションは、指定されたパラメーター型の 1 つの引数を指定するか、コレクションの 要素型の 0 個以上の引数を指定 許可します。 パラメーター コレクションの詳細については、「パラメーター コレクション」で詳しく説明されています。

parameter_collection は省略可能なパラメーターの後に発生する可能性がありますが、既定値を使用することはできません。parameter_collection の引数を省略すると、代わりに空のコレクションが作成されます。

パラメーター コレクション

パラメーター配列 セクションの名前が変更され、次のように調整されます。

params 修飾子で宣言されたパラメーターは、パラメーター コレクションです。 仮パラメーター リストにパラメーター コレクションが含まれている場合は、リストの最後のパラメーターとし、メソッド パラメーター セクションで指定された型にする必要があります。

注意: params 修飾子を、修飾子 inout、または refと組み合わせすることはできません。 注釈

パラメーター コレクションを使用すると、メソッド呼び出しで次の 2 つの方法のいずれかで引数を指定できます。

  • パラメーター コレクションに指定される引数には、パラメーター コレクション型に暗黙的に変換できる単一の式を指定できます。 この場合、パラメーター コレクションは値パラメーターとまったく同じように動作します。
  • または、呼び出しでは、パラメーター コレクションに対して 0 個以上の引数を指定できます。各引数は、パラメーター コレクションの 要素型暗黙的に変換できる式です。 この場合、呼び出しは、引数がコレクション式の式要素として同じ順序で使用されたかのように コレクション式で指定された規則に従って、パラメーター コレクション型のインスタンスを作成し、新しく作成されたコレクション インスタンスを実際の引数として使用します。 コレクション インスタンスを構築するときに、元の 変換されていない 引数が使用されます。

呼び出しで可変数の引数を許可する場合を除き、パラメーター コレクションは同じ型の値パラメーターと正確に等しくなります。

オーバーロードの解決を実行する場合、パラメーター コレクションを持つメソッドは、通常の形式または展開された形式で適用可能な場合があります。 メソッドの展開形式は、メソッドの通常の形式が適用できない場合、および展開されたフォームと同じシグネチャを持つ適用可能なメソッドがまだ同じ型で宣言されていない場合にのみ使用できます。

パラメーター コレクション自体とパラメーター コレクションの要素として同時に使用できる場合、通常の形式と、単一のパラメーター コレクション引数を持つメソッドの展開形式の間にあいまいさが生じる可能性があります。 必要に応じてキャストを挿入したり、コレクション式を使用したりすることで解決できるため、あいまいさは問題ありません。

シグネチャとオーバーロード

シグネチャの修飾子と オーバーロードparams に関するすべてのルールに変化はありません。

適用可能な関数メンバー

該当する関数メンバー セクションは、次のように調整されます。

パラメーター コレクションを含む関数メンバーが通常の形式で適用できない場合、関数メンバーは代わりに、展開されたフォームで適用できます。

  • パラメーター コレクションが配列でない場合、展開されたフォームは言語バージョン C# 12 以下には適用されません。
  • 展開形は、関数メンバー宣言のパラメーター コレクションを、引数リストの引数の数がパラメーターの総数と一致するように パラメーター コレクションの element 型の 0 個以上の値パラメーターと置き換えることでA 構築されます。 A が関数メンバー宣言の固定パラメーターの数よりも少ない引数を持つ場合、関数メンバーの拡張形式を構築できないため、適用できません。
  • さもなければ、A内の各引数に対して、次のいずれかの条件が満たされている場合に、展開されたフォームが適用可能です。
    • 引数のパラメーター受け渡しモードは、対応するパラメーターのパラメーター受け渡しモードと同じです。
      • 固定値パラメーターまたは拡張によって作成された値パラメーターの場合は、引数式から対応するパラメーターの型への暗黙的な変換が存在します。
      • inout、または ref パラメーターの場合、引数式の型は、対応するパラメーターの型と同じです。
    • 引数のパラメーター受け渡しモードが value で、対応するパラメーターのパラメーター受け渡しモードが入力され、引数式から対応するパラメーターの型への暗黙的な変換が存在します

ベター関数メンバー

ベター関数メンバー セクションは、次のように調整されます。

引数リスト A 一連の引数式が {E₁, E₂, ..., Eᵥ} され、2 つの適用可能な関数メンバーが Mᵥ され、パラメーター型が {P₁, P₂, ..., Pᵥ} および {Q₁, Q₂, ..., Qᵥ}を持つ Mₓ がある場合、Mᵥ は、Mₓ よりも 優れた関数メンバー として定義されます。

  • 各引数に対して、Eᵥ から Qᵥ への暗黙的な変換は、Eᵥ から Pᵥへの暗黙的な変換よりも適していません。
  • 少なくとも 1 つの引数の場合、Eᵥ から Pᵥ への変換は、Eᵥ から Qᵥへの変換よりも優れています。

パラメーター型シーケンス {P₁, P₂, ..., Pᵥ}{Q₁, Q₂, ..., Qᵥ} が等しい場合 (つまり、各 Pᵢ が対応する Qᵢへの ID 変換を持つ場合)、より適切な関数メンバーを決定するために、次のタイブレーク ルールが適用されます。

  • Mᵢ が非ジェネリック メソッドであり、Mₑ がジェネリック メソッドである場合、MᵢMₑよりも優れています。
  • それ以外の場合、Mᵢ が通常の形式で適用され、Mₑ に params コレクションがあり、展開された形式でのみ適用できる場合、MᵢMₑよりも優れています。
  • それ以外の場合、両方のメソッドに params コレクションがあり、展開されたフォームでのみ適用できる場合、および Mᵢ の params コレクションの要素が Mₑの params コレクションよりも少ない場合は、Mₑよりも Mᵢ が適しています。
  • それ以外の場合、MᵥMₓよりも具体的なパラメーター型がある場合は、MᵥMₓよりも優れています。 {R1, R2, ..., Rn}{S1, S2, ..., Sn} は、MᵥMₓの未具体化および未展開のパラメーター型を表します。 Mᵥパラメーターの型は、各パラメーターについて、RxSxよりも具体的でない場合 Mₓよりも具体的であり、少なくとも 1 つのパラメーターの場合、RxSxよりも具体的です。
    • 型パラメーターは、型以外のパラメーターよりも具体的ではありません。
    • 少なくとも 1 つの型引数がより具体的であり、かつどの型引数も他の型引数の対応する型引数より具体的でない場合、構築型は (同じ数の型引数がある) 他の構築型より具体的になる
    • 配列型は、1 つ目の要素型が 2 番目の要素型よりも具体的な場合、(次元数が同じ) 別の配列型よりも具体的です。
  • それ以外の場合は、1 つのメンバーが非リフト演算子で、もう一方がリフトされた演算子である場合は、非リフト演算子の方が適しています。
  • どちらの関数メンバーも良好でなく、Mᵥ のすべてのパラメーターに対応する引数があるのに対し、既定の引数は Mₓの少なくとも 1 つの省略可能なパラメーターに置き換える必要がある場合、MᵥMₓよりも優れています。
  • 少なくとも 1 つのパラメーター Mᵥ で、Mₓ の対応するパラメーターよりも 優れたパラメーターパッシング選択(§12.6.4.4) を使用し、Mₓ のどのパラメーターも Mᵥよりも優れたパラメーター渡しの選択肢を使用しない場合、MᵥMₓよりも優れています。
  • それ以外の場合、両方のメソッドにパラメーター コレクションがあり、展開された形式でのみ適用できる場合は、同じ引数のセットが両方のメソッドのコレクション要素に対応し、次のいずれかの保留 (これは https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/collection-expressions-better-conversion.mdに対応する) の場合は、Mₑ よりも Mᵢ が適しています
    • 両方のパラメーター コレクションが span_typeではなく、Mᵢ の params コレクションから Mₑ の params コレクションへの暗黙的な変換が存在します。
    • Mᵢ のパラメータコレクションは System.ReadOnlySpan<Eᵢ>で、Mₑ のパラメータコレクションは System.Span<Eₑ>であり、Eᵢ から Eₑ へのアイデンティティ変換が存在しています。
    • Mᵢ の params コレクションは System.ReadOnlySpan<Eᵢ> または System.Span<Eᵢ>であり、Mₑ の params コレクションは element 型Eₑがある array_or_array_interface__type で、Eᵢ から Eₑ への恒等変換が存在します。
  • それ以外の場合、どの関数メンバーもベターではありません。

新しいタイブレークルールがリストの最後に配置される理由は、最後のサブアイテムです

  • 両方のパラメーター コレクションが span_typeではなく、Mᵢ の params コレクションから Mₑ の params コレクションへの暗黙的な変換が存在します。

これは配列に適用できるため、以前にタイブレークを実行すると、既存のシナリオで動作が変更されます。

例えば:

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 で確認され、承認されました。

これらの規則の効果の 1 つは、異なる要素型の 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 にコンパイル時の型 がない場合、メソッドの呼び出しでは、動的メンバー呼び出しのコンパイル時チェックの §12.6.5 で説明されているように、コンパイル時のチェックが制限されます。

1 つの候補のみがテストに合格した場合、次のすべての条件が満たされると、候補の呼び出しは静的にバインドされます。

  • 候補がローカル関数である
  • 候補がジェネリックでないか、その型引数が明示的に指定されています。
  • コンパイル時に解決できない候補の通常形式と拡張形式の間にあいまいさはありません。

それ以外の場合、invocation_expression は動的にバインドされます。

1 人の候補だけが上記のテストに合格した場合:

  • その候補がローカル関数の場合、コンパイル時エラーが発生します。
  • その候補が、配列以外のパラメーター コレクションを利用する拡張形式でのみ適用できる場合は、コンパイル時エラーが発生します。

また、現在のローカル関数に影響するスペック違反の元に戻す/修正することも検討する必要があります。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 が呼び出される

パラメーター配列の場合、すべての引数が構文の順序で評価された後、ターゲット メソッドが呼び出される直前に配列が作成されることに注意してください。

複合代入。

コレクション インスタンスは、構文的に前のインデックスが評価された後で、構文的に次のインデックスが評価される前に作成され、設定されます。 インスタンスは、ターゲット インデクサーのゲッターとセッターを呼び出すために使用されます。

例えば:

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 が評価され、前の手順で返された C1F1 フィールドに割り当てられます
  5. インデクサーのゲッターは、インデックスのキャッシュされた値で呼び出されます
  6. GetF2 が評価され、前の手順で返された C1F2 フィールドに割り当てられます

パラメーター配列の場合、その要素は評価されてキャッシュされますが、インデクサーのゲッターを呼び出すたびに、配列の新しいインスタンス (内部に同じ値を持つ) が使用されることに注意してください。 上記の例では、評価の順序は次のとおりです。

  1. GetA が呼び出されてキャッシュされる
  2. GetC が呼び出されてキャッシュされる
  3. インデクサーの getter は、キャッシュされた GetA と、キャッシュされた GetC が実装された新しい配列で呼び出されます
  4. GetF1 が評価され、前の手順で返された C1F1 フィールドに割り当てられます
  5. インデクサーの getter は、キャッシュされた GetA と、キャッシュされた GetC が実装された新しい配列で呼び出されます
  6. GetF2 が評価され、前の手順で返された C1F2 フィールドに割り当てられます

空のコレクションを使用した例:

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 が評価され、前の手順で返された C1F1 フィールドに割り当てられます
  5. インデクサーのゲッターは、インデックスのキャッシュされた値で呼び出されます
  6. GetF2 が評価され、前の手順で返された C1F2 フィールドに割り当てられます

ref safety

コレクション式のref セーフティーセクション は、APIが展開された形式で呼び出される際のパラメーターコレクションの構築に適用されます。

パラメーターの型が ref 構造体の場合、パラメーターは暗黙的に scoped されます。 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

paramsref struct パラメーターを変更する場合は、scoped宣言されたものと見なす必要があるという提案があります。 BCL ケースを調べると、パラメーターのスコープを設定するケースの数が実質 100% になるよう、引数が生成されます。 必要な場合は、既定値を [UnscopedRef]で上書きできます。

ただし、単に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 は単独では されず、すべてのレベルで繰り返す必要があります。

提案: 元の定義が scoped パラメーターの場合、params パラメーターのオーバーライドで明示的に params または scoped を指定する必要があります。 つまり、Derived 内の s2 には、paramsscoped、またはその両方が必要です。

解決策:

params 以外のパラメーターが必要な場合は、params パラメーターのオーバーライドに scoped または 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)
    {
    }
}

解決策:

宣言サイトで params パラメーターになる資格を判断するために使用されるコンストラクターに対して、required メンバーを検証します - https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-02-21.md#required-members-and-params-parameters

選択肢

代替案 提案 は、ReadOnlySpan<T>のみを対象として params を拡張します。

また、コレクション式が言語に含まれているなら、params サポートを拡張する必要はまったくないと考えるかもしれません。 任意のコレクション型。 コレクション型で API を使用するには、開発者は、展開された引数のリストの前に [ し、その後に ]、2 つの文字を追加する必要があります。 その場合、params サポートの拡張は過剰である可能性があります。特に、他の言語では、配列以外の params パラメーターの使用をすぐにサポートする可能性は低いということです。