다음을 통해 공유


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입니다.

메서드 호출에서 매개 변수 컬렉션은 지정된 매개 변수 형식의 단일 인수를 지정하거나 컬렉션의 요소 형식 인수를 0개 이상 지정할 수 있도록 허용합니다. 매개 변수 컬렉션에 대해 매개 변수 컬렉션자세히 설명합니다.

parameter_collection 선택적 매개 변수 후에 발생할 수 있지만 기본값을 가질 수 없습니다. parameter_collection 인수를 생략하면 빈 컬렉션이 생성됩니다.

매개 변수 컬렉션

매개 변수 배열 섹션의 이름은 다음과 같이 변경되고 조정됩니다.

params 한정자를 사용하여 선언된 매개 변수는 매개 변수 컬렉션입니다. 공식 매개 변수 목록에 매개 변수 컬렉션이 포함된 경우 이 매개 변수는 목록의 마지막 매개 변수여야 하며 메서드 매개 변수 섹션에 지정된 형식이어야 합니다.

참고: params 한정자를 in, out또는 ref한정자와 결합할 수 없습니다. 끝 메모

매개 변수 컬렉션을 사용하면 메서드 호출의 두 가지 방법 중 하나로 인수를 지정할 수 있습니다.

  • 매개 변수 컬렉션에 지정된 인수는 매개 변수 컬렉션 형식으로 암시적으로 변환할 수 있는 단일 식일 수 있습니다. 이 경우 매개 변수 컬렉션은 값 매개 변수처럼 정확하게 작동합니다.
  • 또는 호출에서 매개 변수 컬렉션에 대해 0개 이상의 인수를 지정할 수 있습니다. 여기서 각 인수는 매개 변수 컬렉션의 요소 유형로 암시적 변환이 가능한 식입니다. 이 경우 호출은 인수가 같은 순서로 컬렉션 식의 식 요소로 사용된 것처럼 컬렉션 식에 지정된 규칙에 따라 매개 변수 컬렉션 형식의 인스턴스를 만들고 새로 만든 컬렉션 인스턴스를 실제 인수로 사용합니다. 컬렉션 인스턴스를 생성할 때 원래 변환되지 않은 인수가 사용됩니다.

호출에서 가변 개수의 인수를 허용하는 경우를 제외하고 매개 변수 컬렉션은 동일한 형식의 값 매개 변수와 정확히 동일합니다.

오버로드 확인을 수행할 때 매개 변수 컬렉션이 있는 메서드는 일반 형식 또는 확장된 형식으로 적용할 수 있습니다. 메서드의 확장된 형식은 메서드의 일반 형식을 적용할 수 없는 경우에만 사용할 수 있으며 확장된 양식과 서명이 같은 해당 메서드가 동일한 형식으로 선언되지 않은 경우에만 사용할 수 있습니다.

매개 변수 컬렉션 자체와 매개 변수 컬렉션의 요소로 동시에 사용할 수 있는 경우 단일 매개 변수 컬렉션 인수를 사용하여 메서드의 일반 양식과 확장된 형식 간에 모호성이 발생할 수 있습니다. 그러나 필요한 경우 캐스트를 삽입하거나 컬렉션 식을 사용하여 해결할 수 있으므로 모호성은 문제가 되지 않습니다.

시그니처 및 오버로딩

params 오버로딩의 수정자에 관한 모든 규칙은 그대로 유지됩니다.

적용 가능한 함수 멤버

적용 가능한 함수 멤버 섹션은 다음과 같이 조정됩니다.

매개 변수 컬렉션을 포함하는 함수 멤버가 일반 형식으로는 적용할 수 없을 경우, 대신 확장된 형식 으로는 적용될 수 있습니다.

  • 매개 변수 컬렉션이 배열이 아닌 경우 확장된 양식은 언어 버전 C# 12 이하에 적용되지 않습니다.
  • 확장된 폼은 함수 멤버 선언의 매개 변수 컬렉션을 매개 변수 컬렉션의 요소 형식의 0개 이상의 값 매개 변수로 바꿔서 인수 목록의 인수 수가 총 매개 변수 수와 일치할 A 있습니다. A 함수 멤버 선언의 고정 매개 변수 수보다 인수 수가 적으면 함수 멤버의 확장된 형식을 생성할 수 없으므로 적용할 수 없습니다.
  • 그렇지 않으면 A각 인수에 대해 다음 중 하나가 true인 경우 확장된 양식이 적용됩니다.
    • 인수의 매개 변수 전달 모드는 해당 매개 변수의 매개 변수 전달 모드와 동일하며,
      • 고정 값 매개 변수 또는 확장에서 만든 값 매개 변수의 경우 인수 식에서 해당 매개 변수의 형식으로 암시적 변환이 존재하거나
      • in, out또는 ref 매개 변수의 경우 인수 식의 형식은 해당 매개 변수의 형식과 동일합니다.
    • 인수의 매개 변수 전달 모드는 값이고, 해당 매개 변수의 매개 변수 전달 모드는 입력이며 인수 식에서 해당 매개 변수의 형식으로 암시적 변환이 존재합니다.

향상된 함수 멤버

Better 함수 멤버 섹션은 다음과 같이 조정됩니다.

인수 목록 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ₑ보다 낫습니다.
  • 그렇지 않으면, 두 메서드 모두 params 컬렉션이 있고 확장된 양식에서만 적용 가능하며, Mᵢ의 params 컬렉션에 있는 요소의 수가 Mₑ의 params 컬렉션보다 적은 경우, MᵢMₑ보다 더 낫습니다.
  • 그렇지 않으면 MᵥMₓ보다 더 구체적인 매개 변수 형식이 있는 경우 MᵥMₓ보다 낫습니다. {R1, R2, ..., Rn}{S1, S2, ..., Sn}MᵥMₓ의 인스턴스화되지 않은 및 확장되지 않은 매개 변수 형식을 나타내도록 합니다. Mᵥ매개 변수 형식은 각 매개 변수에 대해 MₓRx보다 덜 구체적이지 않고 하나 이상의 매개 변수에 대해 SxRx보다 구체적인 경우 Sx보다 더 구체적입니다.
    • 형식 매개 변수는 형식이 아닌 매개 변수보다 덜 구체적입니다.
    • 재귀적으로 생성된 형식은 동일한 수의 형식 인수를 가지는 다른 생성된 형식보다 더 구체적입니다. 이는 적어도 하나의 형식 인수가 다른 형식보다 더 구체적이며, 어떤 형식 인수도 다른 형식 인수보다 덜 구체적이지 않는 경우 성립합니다.
    • 첫 번째 요소 형식이 두 번째 요소 형식보다 더 구체적인 경우 배열 형식은 다른 배열 형식(차원 수가 같음)보다 더 구체적입니다.
  • 그렇지 않으면 한 멤버가 리프트되지 않은 연산자이고 다른 멤버가 리프트된 연산자인 경우 리프트되지 않은 연산자가 더 좋습니다.
  • 함수 멤버 중 어느 것도 더 나은 것으로 판별되지 않았고, 모든 Mᵥ 매개변수가 대응하는 인수를 가지며, Mₓ에서는 하나 이상의 선택적 매개변수에 대해 기본 인수가 대체되어야 하는 경우, MᵥMₓ보다 더 좋습니다.
  • 하나 이상의 매개 변수 Mᵥ 해당 매개 변수보다 더 나은 매개 변수 전달 선택(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_type않으므로, Mᵢ의 params 컬렉션에서 Mₑ 매개 변수 컬렉션으로의 암시적 변환이 존재합니다.
    • Mᵢ 매개 변수 컬렉션이 System.ReadOnlySpan<Eᵢ>Mₑ 매개 변수 컬렉션이 System.Span<Eₑ>id 변환이 EᵢEₑ
    • 매개 변수 컬렉션은 Mᵢ이(가) System.ReadOnlySpan<Eᵢ> 또는 System.Span<Eᵢ>이며, Mₑ의 매개 변수 컬렉션은 array_or_array_interface__type으로, 요소 형식이Eₑ입니다. 그리고 id 변환이 Eᵢ에서 Eₑ로 존재합니다.
  • 그렇지 않으면 더 나은 함수 멤버가 없습니다.

목록 끝에 새 타이브레이커 규칙을 배치하는 이유는 마지막 하위 항목과 관련이 있기 때문입니다.

  • 두 매개 변수 컬렉션이 span_type않으므로, Mᵢ의 params 컬렉션에서 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 컴파일 시간 형식이 없는 경우 메서드 호출은 동적 멤버 호출대한 §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로 불립니다.

매개 변수 배열의 경우 모든 인수가 어휘 순서로 평가된 후 대상 메서드가 호출되기 바로 전에 배열이 만들어집니다.

복합 할당

사전적으로 이전 인덱스를 평가한 후 어휘적으로 다음 인덱스를 평가하기 전에 컬렉션 인스턴스가 만들어지고 채워집니다. 인스턴스는 대상 인덱서의 getter 및 setter를 호출하는 데 사용됩니다.

예를 들어:

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. 인덱서의 getter는 인덱스의 캐시된 값으로 호출됩니다.
  4. 결과가 증가합니다.
  5. 인덱서의 setter는 인덱스에 대해 캐시된 값과 증분 결과로 호출됩니다.

빈 컬렉션이 있는 예제:

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. 인덱서의 getter는 인덱스의 캐시된 값으로 호출됩니다.
  4. 결과가 증가합니다.
  5. 인덱서의 setter는 인덱스에 대해 캐시된 값과 증분 결과로 호출됩니다.

개체 이니셜라이저

사전적으로 이전 인덱스를 평가한 후 어휘적으로 다음 인덱스를 평가하기 전에 컬렉션 인스턴스가 만들어지고 채워집니다. 인스턴스는 필요한 경우 필요한 횟수만큼 인덱서의 getter를 호출하는 데 사용됩니다.

예를 들어:

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. 인덱서의 getter는 인덱스의 캐시된 값으로 호출됩니다.
  4. GetF1 평가되고 이전 단계에서 다시 조정된 F1C1 필드에 할당됩니다.
  5. 인덱서의 getter는 인덱스의 캐시된 값으로 호출됩니다.
  6. GetF2 평가되고 이전 단계에서 다시 조정된 F2C1 필드에 할당됩니다.

매개 변수 배열의 경우 해당 요소는 평가되고 캐시되지만, 인덱서 getter를 호출할 때마다 배열의 새 인스턴스(내부에 동일한 값 포함)가 사용됩니다. 위의 예제에서 평가 순서는 다음과 같습니다.

  1. GetA 호출되고 캐시됩니다.
  2. GetC 호출되고 캐시됩니다.
  3. 인덱서의 getter는 캐시된 GetA 및 캐시된 GetC이 포함된 새 배열로 호출됩니다.
  4. GetF1 평가되고 이전 단계에서 다시 조정된 F1C1 필드에 할당됩니다.
  5. 인덱서의 getter는 캐시된 GetA 및 캐시된 GetC이 포함된 새 배열로 호출됩니다.
  6. GetF2 평가되고 이전 단계에서 다시 조정된 F2C1 필드에 할당됩니다.

빈 컬렉션이 있는 예제:

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. 인덱서의 getter는 인덱스의 캐시된 값으로 호출됩니다.
  4. GetF1 평가되고 이전 단계에서 다시 조정된 F1C1 필드에 할당됩니다.
  5. 인덱서의 getter는 인덱스의 캐시된 값으로 호출됩니다.
  6. GetF2 평가되고 이전 단계에서 다시 조정된 F2C1 필드에 할당됩니다.

참조 안전

컬렉션 식 참조 안전 섹션은 API가 확장된 형태로 호출될 때 매개 변수 컬렉션의 생성에 적용됩니다.

params 매개 변수는 형식이 ref struct일 때 암시적으로 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 매개변수 암시적으로

paramsref struct 매개 변수를 수정할 때 scoped로 선언되어야 한다는 제안이 있었습니다. BCL 사례를 살펴볼 때 매개변수의 범위를 지정하려는 사례 수가 사실상 거의 100개가 된다%라는 주장이 제기됩니다. 필요한 경우에 한해서 몇 가지 사례에서 기본 설정을 [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를 명시적으로 나타내야 합니다. 즉, s2Derived에는 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 매개 변수의 사용을 지원할 가능성이 낮습니다.