다음을 통해 공유


컬렉션 식

비고

이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.

기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련된 언어 디자인 모임(LDM) 노트에 기록되어 있습니다.

C# 언어 표준에 기능 스펙렛을 채택하는 과정에 대해 사양 문서에서 더 자세히 알아볼 수 있습니다.

챔피언 발행호: https://github.com/dotnet/csharplang/issues/8652

요약

컬렉션 식은 공통 컬렉션 값을 만들기 위해 새 terse 구문을 [e1, e2, e3, etc]도입합니다. 다음과 같은 ..e스프레드 요소를 [e1, ..c2, e2, ..c2] 사용하여 다른 컬렉션을 이러한 값에 인라인 처리할 수 있습니다.

외부 BCL 지원을 요구하지 않고 여러 컬렉션과 유사한 형식을 만들 수 있습니다. 이러한 형식은 다음과 같습니다.

형식 자체에서 직접 채택할 수 있는 새 특성 및 API 패턴을 통해 위에서 다루지 않는 컬렉션과 같은 형식에 대한 추가 지원이 제공됩니다.

동기

  • 컬렉션과 유사한 값은 프로그래밍, 알고리즘, 특히 C#/.NET 에코시스템에 매우 존재합니다. 거의 모든 프로그램에서 이러한 값을 활용하여 데이터를 저장하고 다른 구성 요소에서 데이터를 보내거나 받습니다. 현재 거의 모든 C# 프로그램은 이러한 값의 인스턴스를 만들기 위해 다양한 방법 및 불행히도 자세한 접근 방식을 사용해야 합니다. 일부 접근 방식에는 성능 단점도 있습니다. 다음은 몇 가지 일반적인 예입니다.

    • 값 중 하나 new Type[] 또는 new[] 앞에 { ... } 필요한 배열입니다.
    • 사용할 수 stackalloc 있는 범위 및 기타 번거로운 구문입니다.
    • 컬렉션 이니셜라이저는 값 이전에 구문(가능한 자세한 new List<T>정보 유추 부족)과 같은 T 구문이 필요하며 초기 용량을 제공하지 않고 N .Add 호출을 사용하기 때문에 메모리를 여러 개 다시 할당할 수 있습니다.
    • 값을 초기화하는 구문이 ImmutableArray.Create(...) 필요하고 중간 할당 및 데이터 복사가 발생할 수 있는 변경할 수 없는 컬렉션입니다. 보다 효율적인 건설 형태(예: ImmutableArray.CreateBuilder)는 다루기 어렵고 여전히 피할 수 없는 쓰레기를 생성합니다.
  • 주변 에코시스템을 살펴보면 목록 만들기의 모든 곳에서 더 편리하고 사용하기 편한 예제를 찾을 수 있습니다. TypeScript, 다트, Swift, Elm, Python 등은 이 목적을 위해 간결한 구문을 선택하며, 광범위한 사용법과 뛰어난 효과를 제공합니다. 간략한 조사에 따르면 이러한 리터럴이 내장된 에코시스템에서 발생하는 실질적인 문제는 없습니다.

  • C#은 C# 11에도 목록 패턴을 추가했습니다. 이 패턴을 사용하면 깨끗하고 직관적인 구문을 사용하여 목록과 유사한 값을 일치시키고 분해할 수 있습니다. 그러나 거의 모든 다른 패턴 구문과 달리 이 일치/분해 구문에는 해당 생성 구문이 부족합니다.

  • 각 컬렉션 형식을 생성하기 위한 최상의 성능을 얻는 것은 까다로울 수 있습니다. 간단한 솔루션은 CPU와 메모리를 모두 낭비하는 경우가 많습니다. 리터럴 폼을 사용하면 컴파일러 구현에서 최대한 유연하게 리터럴을 최적화하여 사용자가 제공할 수 있는 만큼 좋은 결과를 생성할 수 있지만 간단한 코드를 사용할 수 있습니다. 컴파일러가 더 잘 할 수 있는 경우가 많으며, 이 사양은 구현 전략 측면에서 구현에 많은 여유를 허용하여 이를 보장하는 것을 목표로 합니다.

C#에는 포괄적인 솔루션이 필요합니다. 이미 가지고 있는 컬렉션과 유사한 형식 및 값 측면에서 고객에 대한 대부분의 casse를 충족해야 합니다. 또한 언어에서 자연스럽게 느껴지고 패턴 일치에서 수행된 작업을 미러링해야 합니다.

이렇게 하면 구문이 같 [e1, e2, e3, e-etc] 거나 같은 패턴과 일치해야 한다는 자연스러운 결론이 발생합니다[e1, ..c2, e2][p1, p2, p3, p-etc].[p1, ..p2, p3]

상세 디자인

다음 문법 프로덕션이 추가됩니다.

primary_no_array_creation_expression
  ...
+ | collection_expression
  ;

+ collection_expression
  : '[' ']'
  | '[' collection_element ( ',' collection_element )* ']'
  ;

+ collection_element
  : expression_element
  | spread_element
  ;

+ expression_element
  : expression
  ;

+ spread_element
  : '..' expression
  ;

컬렉션 리터럴은 대상 형식입니다.

사양 설명

  • 간결함을 collection_expression 위해 다음 섹션에서 "리터럴"이라고 합니다.

  • expression_element인스턴스는 일반적으로 등e1이라고 e_n합니다.

  • spread_element인스턴스는 일반적으로 등..s1이라고 ..s_n합니다.

  • 범위 유형 은 둘 중 하나 Span<T> 또는 ReadOnlySpan<T>.

  • 리터럴은 일반적으로 임의의 순서로 요소 수를 전달하는 것으로 [e1, ..s1, e2, ..s2, etc] 표시됩니다. 중요한 것은 이 양식이 다음과 같은 모든 사례를 나타내는 데 사용됩니다.

    • 빈 리터럴 []
    • 리터럴에 없는 expression_element 리터럴입니다.
    • 리터럴에 없는 spread_element 리터럴입니다.
    • 임의의 요소 형식 순서가 있는 리터럴입니다.
  • 반복 형식..s_n 은 반복 변수의 형식으로, 식이 반복되는 동안 사용된 것처럼 s_n 결정됩니다foreach_statement.

  • 로 시작하는 __name 변수는 한 번만 평가되도록 위치에 저장된 평가 name결과를 나타내는 데 사용됩니다. 예를 들어 __e1 .e1

  • List<T>, IEnumerable<T>등은 네임스페이스의 해당 형식을 System.Collections.Generic 참조합니다.

  • 이 사양은 리터럴을 기존 C# 구문으로 변환 하는 방법을 정의합니다. 쿼리 식 변환과 마찬가지로 리터럴 자체는 번역에 법적 코드가 발생하는 경우에만 유효합니다. 이 규칙의 목적은 암시된 언어의 다른 규칙을 반복할 필요가 없도록 하는 것입니다(예: 스토리지 위치에 할당될 때 식의 변환 가능성에 대한 정보).

  • 구현은 아래 지정된 대로 리터럴을 정확하게 변환할 필요가 없습니다. 동일한 결과가 생성되고 결과 생산에 관찰 가능한 차이가 없는 경우 모든 번역이 합법적입니다.

    • 예를 들어 구현은 직접 같은 [1, 2, 3] 리터럴을 직접 식으로 변환하여 new int[] { 1, 2, 3 } 원시 데이터를 어셈블리로 굽는 식으로 변환하여 각 값을 할당하기 위한 __index 지침의 필요성 또는 시퀀스를 유도할 수 있습니다. 중요한 것은 변환 단계가 런타임에 프로그램 상태가 번역으로 표시된 상태로 남아 있다는 예외를 일으킬 수 있는 경우를 의미합니다.
  • '스택 할당'에 대한 참조는 힙이 아닌 스택에 할당하는 모든 전략을 참조합니다. 중요한 것은 해당 전략이 실제 stackalloc 메커니즘을 통해 수행된다는 것을 암시하거나 요구하지 않는다는 것입니다. 예를 들어 인 라인 배열 을 사용하는 것은 사용 가능한 경우 스택 할당을 수행하는 허용되고 바람직한 방법이기도 합니다. C# 12에서는 컬렉션 식을 사용하여 인라인 배열을 초기화할 수 없습니다. 그것은 열린 제안으로 남아 있습니다.

  • 컬렉션은 잘 동작하는 것으로 간주됩니다. 다음은 그 예입니다.

    • 컬렉션의 Count 값이 열거될 때 요소 수와 동일한 값을 생성한다고 가정합니다.
    • 네임스페이스에 정의된 이 사양에 System.Collections.Generic 사용된 형식은 부작용이 없는 것으로 추정됩니다. 따라서 컴파일러는 이러한 형식을 중간 값으로 사용할 수 있지만 그렇지 않으면 노출되지 않는 시나리오를 최적화할 수 있습니다.
    • 컬렉션에서 적용 가능한 .AddRange(x) 일부 멤버를 호출하면 반복하고 열거된 모든 값을 컬렉션에 개별적으로 추가하는 것과 동일한 최종 값 x.Add발생한다고 가정합니다.
    • 잘 동작하지 않는 컬렉션이 있는 컬렉션 리터럴의 동작은 정의되지 않습니다.

Conversions

컬렉션 식 변환을 사용하면 컬렉션 식을 형식으로 변환할 수 있습니다.

암시적 컬렉션 식 변환 은 컬렉션 식에서 다음 형식으로 존재합니다.

  • 단일 차원 배열 형식T[](이 경우 요소 형식 은 )입니다. T
  • 범위 유형:
    • System.Span<T>
    • System.ReadOnlySpan<T>
      이 경우 요소 형식 은 입니다. T
  • 적절한 create 메서드가 있는 형식입니다. 이 경우 요소 형식은 확장 메서드가 아닌 인스턴스 메서드 또는 열거 가능한 인터페이스에서 결정 GetEnumerator입니다.
  • 다음 위치를 구현하는 구조체 또는 클래스 형식입니다.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

형식에 컬렉션 식의 각 요소T에 대한 요소 형식Eᵢ이 있는 경우 암시적 변환이 존재합니다.

  • Eᵢ인 경우 암시적 변환 EᵢT이 있습니다.
  • Eᵢ 인 경우 ..Sᵢ반복 형식에서 .로의 Sᵢ 암시적 변환이 T있습니다.

컬렉션 식에서 다차원 배열 형식으로의 컬렉션 식 변환은 없습니다.

컬렉션 식에서 암시적 컬렉션 식 변환이 있는 형식은 해당 컬렉션 식에 유효한 대상 형식 입니다.

컬렉션 식에서 다음과 같은 추가 암시적 변환이 존재합니다.

  • 컬렉션 식에서 값 형식T? 으로 의 컬렉션 식 변환 이 있는 nullable 값 형식 T으로. 변환은 컬렉션 식 변환T암시적 nullable 변환 을 .로 T 변환하는 것입니다 T?.

  • 연결된 Tcreate 메서드가 있는 참조 형식 T 으로 형식 및 U 을 반환 U 합니다T. 변환은 컬렉션 식 변환U암시적 참조 변환 에서 UT.

  • 연결된 I 가 있는 인터페이스 형식 I 으로 형식 및 V 을 반환 V 합니다I. 변환은 컬렉션 식 변환V암시적 boxing 변환 에서 V .로 변환됩니다 I.

메서드 만들기

create 메서드[CollectionBuilder(...)] 특성으로 표시됩니다. 이 특성은 컬렉션 형식의 인스턴스를 생성하기 위해 호출할 메서드의 작성기 형식메서드 이름을 지정합니다.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(
        AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
        Inherited = false,
        AllowMultiple = false)]
    public sealed class CollectionBuilderAttribute : System.Attribute
    {
        public CollectionBuilderAttribute(Type builderType, string methodName);
        public Type BuilderType { get; }
        public string MethodName { get; }
    }
}

특성은 , class, struct또는 ref struct.에 interface적용할 수 있습니다. 특성은 기본 class 또는 abstract class에 적용할 수 있지만 상속되지 않습니다.

작성기 형식은 제네릭 class 이 아닌 형식이거나 struct.

먼저 적용 가능한 만들기 메서드CM 집합이 결정됩니다.
다음 요구 사항을 충족하는 메서드로 구성됩니다.

  • 메서드는 특성에 [CollectionBuilder(...)] 지정된 이름이 있어야 합니다.
  • 작성기 형식 에서 직접 메서드를 정의해야 합니다.
  • 메서드는 .이어야 static합니다.
  • 컬렉션 식이 사용되는 위치에 메서드에 액세스할 수 있어야 합니다.
  • 메서드의 arity 는 컬렉션 형식의 결과 와 일치해야 합니다.
  • 메서드에는 값으로 전달되는 형식 System.ReadOnlySpan<E>의 단일 매개 변수가 있어야 합니다.
  • 메서드 반환 형식에서 컬렉션 형식으로의 ID 변환, 암시적 참조 변환 또는 boxing 변환이 있습니다.

기본 형식 또는 인터페이스에 선언된 메서드는 무시되며 집합의 CM 일부가 아닙니다.

집합이 CM 비어 있으면 컬렉션 형식요소 형식 이 없고 create 메서드가 없습니다. 다음 단계는 적용되지 않습니다.

집합에 있는 CM 메서드 중 하나의 메서드만 컬렉션 형식요소 형식으로 E의 ID 변환이 있는 경우 컬렉션 형식에 대한 create 메서드입니다. 그렇지 않으면 컬렉션 형식create 메서드가 없습니다.

특성이 예상 시그니처가 [CollectionBuilder] 있는 호출 가능한 메서드를 참조하지 않는 경우 오류가 보고됩니다.

형식 선언C<S0, S1, …>에 연결된 작성기 메서드C<T0, T1, …>가 있는 대상 형식 이 있는 B.M<U0, U1, …>()의 경우 대상 형식의 제네릭 형식 인수가 순서대로 적용되고 가장 바깥쪽에 포함된 형식에서 가장 안쪽에 있는 제네릭 형식 인수가 작성기 메서드에 적용됩니다.

create 메서드에 대한 범위 매개 변수를 명시적으로 표시 scoped 하거나 [UnscopedRef]. 매개 변수가 암시적 또는 명시적으로 scoped인 경우 컴파일러는 힙이 아닌 스택의 범위에 대한 스토리지를 할당할 수 있습니다 .

예를 들어 다음과 같은 가능한 create 메서드가 있습니다.ImmutableArray<T>

[CollectionBuilder(typeof(ImmutableArray), "Create")]
public struct ImmutableArray<T> { ... }

public static class ImmutableArray
{
    public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items) { ... }
}

위의 ImmutableArray<int> ia = [1, 2, 3];를 사용하면 다음과 같이 내보냅니다.

[InlineArray(3)] struct __InlineArray3<T> { private T _element0; }

Span<int> __tmp = new __InlineArray3<int>();
__tmp[0] = 1;
__tmp[1] = 2;
__tmp[2] = 3;
ImmutableArray<int> ia =
    ImmutableArray.Create((ReadOnlySpan<int>)__tmp);

Construction

컬렉션 식의 요소는 왼쪽에서 오른쪽으로 순서대로 계산 됩니다. 각 요소는 정확히 한 번 평가되며 요소에 대한 추가 참조는 이 초기 평가의 결과를 참조합니다.

컬렉션 식의 후속 요소가 평가되기 전이나 후에 스프레드 요소를 반복할 수 있습니다.

생성 중에 사용되는 메서드에서 throw된 처리되지 않은 예외는 catch되지 않으며 생성의 추가 단계를 방지합니다.

Length, Count그리고 GetEnumerator 부작용이 없는 것으로 간주됩니다.


대상 형식이 구현하는 구조체 또는 System.Collections.IEnumerable이고 대상 형식에 create 메서드가 없는 경우 컬렉션 인스턴스의 생성은 다음과 같습니다.

  • 요소는 순서대로 평가됩니다. 일부 또는 모든 요소는 이전이 아닌 아래 단계에서 평가할 수 있습니다.

  • 컴파일러는 각 스프레드 요소 식에서 개수 있는 속성 또는 잘 알려진 인터페이스 또는 형식의 해당 속성을 호출하여 컬렉션 식의 알려진 길이를 결정할 수 있습니다.

  • 인수 없이 적용할 수 있는 생성자가 호출됩니다.

  • 각 요소의 순서:

    • 요소가 식 요소인 경우 해당 Add 인스턴스 또는 확장 메서드는 요소 식을 인수로 사용하여 호출됩니다. (클래식 컬렉션 이니셜라이저 동작과 달리 요소 평가 및 Add 호출이 반드시 인터리브되는 것은 아닙니다.)
    • 요소가 스프레드 요소 인 경우 다음 중 하나가 사용됩니다.
      • 적용 가능한 GetEnumerator 인스턴스 또는 확장 메서드는 spread 요소 식 에서 호출되고 열거자의 각 항목에 대해 해당 Add 인스턴스 또는 확장 메서드가 컬렉션 인스턴스 에서 인수로 호출됩니다. 열거자가 구현하는 경우 예외에 IDisposableDispose 관계없이 열거 후 호출됩니다.
      • 적용 가능한 AddRange 인스턴스 또는 확장 메서드는 spread 요소 식을 인수로 사용하여 컬렉션 인스턴스에서 호출됩니다.
      • 적용 가능한 CopyTo 인스턴스 또는 확장 메서드는 컬렉션 인스턴스와 인덱스가 인수로 포함된 int에서 호출됩니다.
  • 위의 생성 단계에서는 용량 인수를 사용하여 EnsureCapacity 인스턴스에서 해당 인스턴스 또는 확장 메서드를 하나 이상 호출할 int.


대상 형식이 배열, 범위, create 메서드가 있는 형식 또는 인터페이스인 경우 컬렉션 인스턴스의 생성은 다음과 같습니다.

  • 요소는 순서대로 평가됩니다. 일부 또는 모든 요소는 이전이 아닌 아래 단계에서 평가할 수 있습니다.

  • 컴파일러는 각 스프레드 요소 식에서 개수 있는 속성 또는 잘 알려진 인터페이스 또는 형식의 해당 속성을 호출하여 컬렉션 식의 알려진 길이를 결정할 수 있습니다.

  • 초기화 인스턴스는 다음과 같이 만들어집니다.

    • 대상 형식이 배열 이고 컬렉션 식의 길이가 알려진 경우 배열은 예상 길이로 할당됩니다.
    • 대상 형식이 create 메서드가 있는 범위 또는 형식이고 컬렉션에 알려진 길이가 있는 경우 연속 스토리지를 참조하는 예상 길이가 있는 범위가 만들어집니다.
    • 그렇지 않으면 중간 스토리지가 할당됩니다.
  • 각 요소의 순서:

    • 요소가 식 요소인 경우 현재 인덱스에서 평가된 식을 추가하기 위해 초기화 인스턴스 인덱서 가 호출됩니다.
    • 요소가 스프레드 요소 인 경우 다음 중 하나가 사용됩니다.
      • 잘 알려진 인터페이스 또는 형식의 멤버는 spread 요소 식에서 초기화 인스턴스로 항목을 복사하기 위해 호출됩니다.
      • 적용 가능한 GetEnumerator 인스턴스 또는 확장 메서드는 spread 요소 식 에서 호출되고 열거자의 각 항목에 대해 초기화 인스턴스 인덱서 가 호출되어 현재 인덱스에서 항목을 추가합니다. 열거자가 구현하는 경우 예외에 IDisposableDispose 관계없이 열거 후 호출됩니다.
      • 적용 가능한 CopyTo 인스턴스 또는 확장 메서드는 초기화 인스턴스와 인덱스가 인수로 포함된 int에서 호출됩니다.
  • 컬렉션에 대해 중간 스토리지가 할당된 경우 컬렉션 인스턴스가 실제 컬렉션 길이로 할당되고 초기화 인스턴스의 값이 컬렉션 인스턴스에 복사되거나 범위가 필요한 경우 컴파일러는 중간 스토리지에서 실제 컬렉션 길이의 범위를 사용할 수 있습니다 . 그렇지 않으면 초기화 인스턴스가 컬렉션 인스턴스입니다.

  • 대상 형식에 create 메서드가 있는 경우 create 메서드는 범위 인스턴스를 사용하여 호출됩니다.


메모: 컴파일러는 후속 요소를 평가한 후까지 컬렉션에 요소 추가를 지연 하거나 분산 요소 반복을 지연 할 수 있습니다. (후속 스프레드 요소에 컬렉션을 할당하기 전에 컬렉션의 예상 길이를 계산할 수 있는 셀 수 있는 속성이 있는 경우) 반대로 컴파일러는 지연에 대한 이점이 없는 경우 컬렉션에 요소를 적극적으로 추가하고 확산 요소를 열심히 반복할 수 있습니다.

다음 컬렉션 식을 고려합니다.

int[] x = [a, ..b, ..c, d];

요소를 분산 b 하고 c개수를 계산할 수 있는 경우 컴파일러는 계산될 때까지 a 항목 bc 추가를 지연하여 결과 배열을 예상 길이로 할당할 수 있습니다. 그런 다음, 컴파일러는 평가하기 전에 항목을 즉시 추가할 수 있습니다 .

var __tmp1 = a;
var __tmp2 = b;
var __tmp3 = c;
var __result = new int[2 + __tmp2.Length + __tmp3.Length];
int __index = 0;
__result[__index++] = __tmp1;
foreach (var __i in __tmp2) __result[__index++] = __i;
foreach (var __i in __tmp3) __result[__index++] = __i;
__result[__index++] = d;
x = __result;

빈 컬렉션 리터럴

  • 빈 리터럴 [] 에는 형식이 없습니다. 그러나 null 리터럴과 마찬가지로 이 리터럴은 생성 가능한 컬렉션 형식으로 암시적으로 변환될 수 있습니다.

    예를 들어 대상 형식 이 없고 관련된 다른 변환이 없으므로 다음 항목이 유효하지 않습니다.

    var v = []; // illegal
    
  • 빈 리터럴을 퍼뜨리면 생략할 수 있습니다. 다음은 그 예입니다.

    bool b = ...
    List<int> l = [x, y, .. b ? [1, 2, 3] : []];
    

    여기서 false이면 b 최종 리터럴에서 0 값으로 즉시 분산되므로 빈 컬렉션 식에 대한 값을 실제로 생성할 필요는 없습니다.

  • 변경할 수 없는 것으로 알려진 최종 컬렉션 값을 생성하는 데 사용되는 경우 빈 컬렉션 식은 싱글톤으로 허용됩니다. 다음은 그 예입니다.

    // Can be a singleton, like Array.Empty<int>()
    int[] x = []; 
    
    // Can be a singleton. Allowed to use Array.Empty<int>(), Enumerable.Empty<int>(),
    // or any other implementation that can not be mutated.
    IEnumerable<int> y = [];
    
    // Must not be a singleton.  Value must be allowed to mutate, and should not mutate
    // other references elsewhere.
    List<int> z = [];
    

참조 안전

safe-context 값의 정의에 대한 안전한 컨텍스트 제약 조건(declaration-block, function-membercaller-context)을 참조하세요.

컬렉션 식의 안전 컨텍스트 는 다음과 같습니다.

  • 빈 컬렉션 식 [] 의 안전 컨텍스트는 호출자 컨텍스트입니다.

  • 대상 형식이 범위 형식System.ReadOnlySpan<T>이고 T기본 형식bool 중 하나인 경우, sbyte, ,byte, short,ushort, char, intuintlongulongfloat또는 double컬렉션 식에 상수 값만 포함되는 경우 컬렉션 식의 안전 컨텍스트는 호출자 컨텍스트입니다.

  • 대상 형식이 범위 형식System.Span<T> 이거나 System.ReadOnlySpan<T>컬렉션 식의 안전 컨텍스트가 선언 블록인 경우

  • 대상 형식이 create 메서드를 사용하는 ref 구조체 형식인 경우 컬렉션 식의 safe-context는 컬렉션 식이 메서드에 대한 범위 인수인 create 메서드 호출의 안전 컨텍스트입니다.

  • 그렇지 않으면 컬렉션 식의 안전 컨텍스트가 호출자 컨텍스트입니다.

선언 블록의 안전 컨텍스트가 있는 컬렉션 식은 바깥쪽 범위를 이스케이프할 수 없으며 컴파일러는 힙이 아닌 스택에 컬렉션을 저장할 수 있습니다.

ref 구조체 형식에 대한 컬렉션 식이 선언 블록을 이스케이프할 수 있도록 하려면 식을 다른 형식으로 캐스팅해야 할 수 있습니다.

static ReadOnlySpan<int> AsSpanConstants()
{
    return [1, 2, 3]; // ok: span refers to assembly data section
}

static ReadOnlySpan<T> AsSpan2<T>(T x, T y)
{
    return [x, y];    // error: span may refer to stack data
}

static ReadOnlySpan<T> AsSpan3<T>(T x, T y, T z)
{
    return (T[])[x, y, z]; // ok: span refers to T[] on heap
}

형식 유추

var a = AsArray([1, 2, 3]);          // AsArray<int>(int[])
var b = AsListOfArray([[4, 5], []]); // AsListOfArray<int>(List<int[]>)

static T[] AsArray<T>(T[] arg) => arg;
static List<T[]> AsListOfArray<T>(List<T[]> arg) => arg;

형식 유추 규칙은 다음과 같이 업데이트됩니다.

첫 번째 단계에 대한 기존 규칙은 새 입력 형식 유추 섹션으로 추출되고 컬렉션 식 식에 대한 입력 형식 유추출력 형식 유추에 규칙이 추가됩니다.

11.6.3.2 첫 번째 단계

각 메서드 인수 Eᵢ에 대해 다음을 수행합니다.

  • 입력 형식 유추는 해당 매개 변수 형식Eᵢ에서 만들어집니다.Tᵢ

입력 형식 유추는 다음과 같은 방식으로 식 E 에서

  • 요소가 있는 E이고 요소 EᵢT이 있는 형식이거나 TₑT이고 T0?T0이 있는 경우 Tₑ 각각Eᵢ에 대해 다음을 수행합니다.
  • [첫 번째 단계에서 기존 규칙] ...

11.6.3.7 출력 형식 유추

출력 형식 유추는 다음과 같은 방식으로 식 E 에서

  • 요소가 있는 E이고 요소 EᵢT이 있는 형식이거나 TₑT이고 T0?T0이 있는 경우 Tₑ 각각Eᵢ에 대해 다음을 수행합니다.
    • Eᵢ 경우 출력 형식 유추EᵢTₑ.
    • Eᵢ인 경우 유추가 만들어지지 Eᵢ않습니다.
  • [출력 형식 유추의 기존 규칙] ...

확장 메서드

확장 메서드 호출 규칙에 대한 변경 내용이 없습니다.

12.8.10.3 Extension 메서드 호출

다음과 같은 경우 확장 메서드 Cᵢ.Mₑ사용할 수 있습니다 .

  • ...
  • 암시적 ID, 참조 또는 boxing 변환이 expr 에서 첫 번째 매개 변수 Mₑ의 형식으로 존재합니다.

컬렉션 식에는 자연 형식이 없으므로 형식 의 기존 변환을 적용할 수 없습니다. 따라서 컬렉션 식을 확장 메서드 호출의 첫 번째 매개 변수로 직접 사용할 수 없습니다.

static class Extensions
{
    public static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
}

var x = [1].AsImmutableArray();           // error: collection expression has no target type
var y = [2].AsImmutableArray<int>();      // error: ...
var z = Extensions.AsImmutableArray([3]); // ok

오버로드 해결

컬렉션 식 변환에서 특정 대상 형식을 선호하도록 식에서 더 나은 변환이 업데이트됩니다.

업데이트된 규칙에서:

  • span_type 다음 중 하나입니다.
    • System.Span<T>
    • System.ReadOnlySpan<T>;
  • array_or_array_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>

암시적 변환 C₁은(는) 식 E을(를) 형식 T₁로 변환하고, 암시적 변환 C₂은(는) 식 E을(를) 형식 T₂로 변환하는 경우, 다음 중 하나에 해당하면 C₁이(가) 보다 입니다.

  • E 는 컬렉션 식이며 다음 중 하나입니다.
    • T₁System.ReadOnlySpan<E₁>이고 T₂System.Span<E₂>암시적 변환이 에서 로 E₁E₂
    • T₁ System.ReadOnlySpan<E₁>System.Span<E₁>T₂ 있는 E₂, 암시적 변환이 E₁ 있는 경우E₂
    • 은 span_type 아니며 span_type 아니며 암시적 변환이 />로 존재합니다.
  • E컬렉션 식 이 아니며 다음 중 하나가 유지됩니다.
    • E 정확히 일치 T₁ 하며 E 정확히 일치하지 않습니다. T₂
    • E 정확히 둘 다 또는 둘 다와 일치하지 않으며 T₁T₂, T₁ 보다 더 나은 변환 대상 입니다. T₂
  • E 는 메서드 그룹입니다.

배열 이니셜라이저와 컬렉션 식 간의 오버로드 확인과 차이점의 예:

static void Generic<T>(Span<T> value) { }
static void Generic<T>(T[] value) { }

static void SpanDerived(Span<string> value) { }
static void SpanDerived(object[] value) { }

static void ArrayDerived(Span<object> value) { }
static void ArrayDerived(string[] value) { }

// Array initializers
Generic(new[] { "" });      // string[]
SpanDerived(new[] { "" });  // ambiguous
ArrayDerived(new[] { "" }); // string[]

// Collection expressions
Generic([""]);              // Span<string>
SpanDerived([""]);          // Span<string>
ArrayDerived([""]);         // ambiguous

범위 형식

범위 형식 ReadOnlySpan<T> 이며 Span<T> 생성 가능한 컬렉션 형식입니다. 지원은 에 대한 params Span<T>디자인을 따릅니다. 특히 매개 변수 배열이 컴파일러에서 설정한 제한(있는 경우)에 속하는 경우 이러한 범위 중 하나를 생성하면 스택 에 배열 T[]가 만들어집니다. 그렇지 않으면 배열이 힙에 할당됩니다.

컴파일러가 스택에 할당하도록 선택하는 경우 리터럴을 해당 특정 지점으로 stackalloc 직접 변환할 필요가 없습니다. 예를 들어 다음과 같습니다.

foreach (var x in y)
{
    Span<int> span = [a, b, c];
    // do things with span
}

컴파일러는 의미가 동일하게 유지되고 Span이 유지되는 한 해당 사용을 변환할 수 있습니다. 예를 들어 위의 내용은 다음과 같이 변환할 수 있습니다.

Span<int> __buffer = stackalloc int[3];
foreach (var x in y)
{
    __buffer[0] = a
    __buffer[1] = b
    __buffer[2] = c;
    Span<int> span = __buffer;
    // do things with span
}

컴파일러는 스택에 할당하도록 선택할 때 인 라인 배열(사용 가능한 경우)을 사용할 수도 있습니다. C# 12에서는 컬렉션 식을 사용하여 인라인 배열을 초기화할 수 없습니다. 이 기능은 공개 제안입니다.

컴파일러가 힙에 할당하기로 결정한 경우 변환 Span<T> 은 다음과 같습니다.

T[] __array = [...]; // using existing rules
Span<T> __result = __array;

컬렉션 리터럴 변환

컬렉션 식에 있는 각 스프레드 요소의 컴파일 시간 형식이 개수일 경우 컬렉션 식의 알려진 길이가 있습니다.

인터페이스 변환

변경할 수 없는 인터페이스 변환

변경 멤버를 포함하지 않는 대상 형식, 즉IEnumerable<T>, IReadOnlyCollection<T>IReadOnlyList<T>해당 인터페이스를 구현하는 값을 생성하려면 규격 구현이 필요합니다. 형식이 합성된 경우 합성된 형식은 대상으로 지정된 인터페이스 형식에 ICollection<T>관계없이 이러한 모든 인터페이스뿐만 IList<T> 아니라 구현하는 것이 좋습니다. 이렇게 하면 성능 최적화를 밝게 하기 위해 값에 의해 구현된 인터페이스를 소개하는 라이브러리를 포함하여 기존 라이브러리와의 최대 호환성을 보장합니다.

또한 값은 비제네릭 ICollectionIList 인터페이스를 구현해야 합니다. 이렇게 하면 컬렉션 식이 데이터 바인딩과 같은 시나리오에서 동적 내성을 지원할 수 있습니다.

규격 구현은 다음을 수행할 수 있습니다.

  1. 필요한 인터페이스를 구현하는 기존 형식을 사용합니다.
  2. 필요한 인터페이스를 구현하는 형식을 합성합니다.

두 경우 모두 사용되는 형식은 엄격하게 필요한 것보다 더 큰 인터페이스 집합을 구현할 수 있습니다.

합성된 형식은 필요한 인터페이스를 제대로 구현하려는 전략을 자유롭게 사용할 수 있습니다. 예를 들어 합성된 형식은 추가 내부 컬렉션 할당이 필요하지 않도록 요소 자체 내에서 직접 인라인할 수 있습니다. 합성된 형식은 어떤 스토리지도 사용할 수 없으므로 값을 직접 계산하도록 선택할 수 있습니다. 예를 들어 .index + 1[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  1. 값은 (구현된 경우) 및 비제네릭 trueICollection<T>.IsReadOnly에 대해 IList.IsReadOnly 쿼리할 때 반환 IList.IsFixedSize 해야 합니다. 이렇게 하면 소비자가 변경 가능한 뷰를 구현했음에도 불구하고 컬렉션이 변경할 수 없음을 적절하게 알 수 있습니다.
  2. 이 값은 변형 메서드(예: IList<T>.Add)에 대한 호출을 throw해야 합니다. 이렇게 하면 안전을 보장하여 변경할 수 없는 컬렉션이 실수로 변경되지 않도록 방지합니다.

변경 가능한 인터페이스 변환

변경 멤버를 포함하는 지정된 대상 형식( 즉, 다음과 같습니다.ICollection<T>IList<T>)

  1. 값은 .의 List<T>인스턴스여야 합니다.

알려진 길이 변환

알려진 길이를 사용하면 데이터를 복사할 가능성이 없고 결과에 불필요한 여유 공간이 없는 결과를 효율적으로 생성할 수 있습니다.

알려진 길이가 없으면 결과가 생성되지 않습니다. 그러나 추가 CPU 및 메모리 비용이 데이터를 생성한 다음 최종 대상으로 이동할 수 있습니다.

  • 알려진 길이 리터럴[e1, ..s1, etc]의 경우 변환은 먼저 다음으로 시작됩니다.

    int __len = count_of_expression_elements +
                __s1.Count;
                ...
                __s_n.Count;
    
  • 해당 리터럴의 대상 형식 T 이 지정되었습니다.

    • 일부 T경우 T1[] 리터럴은 다음과 같이 변환됩니다.

      T1[] __result = new T1[__len];
      int __index = 0;
      
      __result[__index++] = __e1;
      foreach (T1 __t in __s1)
          __result[__index++] = __t;
      
      // further assignments of the remaining elements
      

      구현은 배열을 채우는 다른 방법을 활용할 수 있습니다. 예를 들어 , 와 같은 .CopyTo()효율적인 대량 복사 메서드를 활용합니다.

    • 일부 T경우 Span<T1> 초기화가 다음과 같이 변환된다는 점을 제외하고 __result 리터럴은 위와 동일하게 변환됩니다.

      Span<T1> __result = new T1[__len];
      
      // same assignments as the array translation
      

      stackalloc T1[] 이 유지되는 경우 대신 변환에서 사용하거나 new T1[]을 사용할 수 있습니다.

    • 일부인 경우 T 최종 결과가 ReadOnlySpan<T1>된다는 점을 제외하고 리터럴은 사례와 Span<T1> 동일하게 변환Span<T1>됩니다.ReadOnlySpan<T1>

      ReadOnlySpan<T1> 일부 기본 형식이고 모든 컬렉션 요소가 상수인 경우 T1 해당 데이터가 힙 또는 스택에 있을 필요가 없습니다. 예를 들어 구현은 프로그램의 데이터 세그먼트 부분에 대한 참조로 이 범위를 직접 생성할 수 있습니다.

      위의 양식(배열 및 범위의 경우)은 컬렉션 식의 기본 표현이며 다음 변환 규칙에 사용됩니다.

      • T 해당하는 C<S0, S1, …>가 있는 경우 B.M<U0, U1, …>() 리터럴은 다음과 같이 변환됩니다.

        // Collection literal is passed as is as the single B.M<...>(...) argument
        C<S0, S1, …> __result = B.M<S0, S1, …>([...])
        

        create 메서드에는 인스턴스화된 ReadOnlySpan<T>일부 인수 형식이 있어야 하므로 컬렉션 식을 create 메서드에 전달할 때 범위에 대한 변환 규칙이 적용됩니다.

      • T 지원하는 경우 다음을 수행합니다.

        • 형식 T 에 단일 매개 변수 int capacity가 있는 액세스 가능한 생성자가 포함되어 있으면 리터럴은 다음과 같이 변환됩니다.

          T __result = new T(capacity: __len);
          __result.Add(__e1);
          foreach (var __t in __s1)
              __result.Add(__t);
          
          // further additions of the remaining elements
          

          참고: 매개 변수의 이름은 .이어야 capacity합니다.

          이 양식을 사용하면 리터럴이 내부 스토리지를 효율적으로 할당할 수 있도록 새로 생성된 요소 개수 유형을 알릴 수 있습니다. 이렇게 하면 요소가 추가될 때 낭비되는 재할당을 방지할 수 있습니다.

        • 그렇지 않으면 리터럴이 다음과 같이 변환됩니다.

          T __result = new T();
          
          __result.Add(__e1);
          foreach (var __t in __s1)
              __result.Add(__t);
          
          // further additions of the remaining elements
          

          이렇게 하면 용량 최적화 없이도 대상 유형을 만들어 스토리지의 내부 재할당을 방지할 수 있습니다.

알 수 없는 길이 변환

  • T 리터럴의 대상 형식 이 지정됩니다.

    • T 지원하는 경우 리터럴은 다음과 같이 변환됩니다.

      T __result = new T();
      
      __result.Add(__e1);
      foreach (var __t in __s1)
          __result.Add(__t);
      
      // further additions of the remaining elements
      

      이렇게 하면 가능한 최소한의 최적화를 통해 반복 가능한 형식을 분산할 수 있습니다.

    • 일부 T경우 T1[] 리터럴의 의미 체계는 다음과 같습니다.

      List<T1> __list = [...]; /* initialized using predefined rules */
      T1[] __result = __list.ToArray();
      

      위의 내용은 비효율적입니다. 중간 목록을 만든 다음 최종 배열의 복사본을 만듭니다. 구현은 다음과 같은 코드를 생성하는 것과 같이 이 작업을 자유롭게 최적화할 수 있습니다.

      T1[] __result = <private_details>.CreateArray<T1>(
          count_of_expression_elements);
      int __index = 0;
      
      <private_details>.Add(ref __result, __index++, __e1);
      foreach (var __t in __s1)
          <private_details>.Add(ref __result, __index++, __t);
      
      // further additions of the remaining elements
      
      <private_details>.Resize(ref __result, __index);
      

      이렇게 하면 라이브러리 컬렉션에 발생할 수 있는 추가 오버헤드 없이 낭비 및 복사를 최소화할 수 있습니다.

      전달된 CreateArray 개수는 낭비되는 크기 조정을 방지하기 위해 시작 크기 힌트를 제공하는 데 사용됩니다.

    • 일부 T인 경우 구현은 위의 T[] 전략 또는 의미 체계가 동일하지만 성능이 더 나은 다른 전략을 따를 수 있습니다. 예를 들어 배열을 목록 요소 CollectionsMarshal.AsSpan(__list) 의 복사본으로 할당하는 대신 범위 값을 직접 가져오는 데 사용할 수 있습니다.

지원되지 않는 시나리오

컬렉션 리터럴은 많은 시나리오에서 사용할 수 있지만 바꿀 수 없는 몇 가지가 있습니다. 여기에는 다음이 포함됩니다.

  • 다차원 배열(예: new int[5, 10] { ... }). 차원을 포함할 수 있는 기능이 없으며 모든 컬렉션 리터럴은 선형 또는 지도 구조만입니다.
  • 생성자에 특수 값을 전달하는 컬렉션입니다. 사용 중인 생성자에 액세스할 수 있는 기능이 없습니다.
  • 중첩된 컬렉션 이니셜라이저(예: new Widget { Children = { w1, w2, w3 } } 이 양식은 의미 체계가 매우 다르므로 유지되어야 합니다 Children = [w1, w2, w3]. 전자는 반복적으로 .Add 호출 .Children 하고 후자는 새 컬렉션을 할당합니다.Children. 할당할 수 없는 경우 .Children 후자의 양식이 기존 컬렉션에 추가하는 것으로 대체되도록 고려할 수 있지만 매우 혼란스러울 수 있습니다.

구문 모호성

  • 을 사용하는 collection_literal_expression코드의 여러 법적 구문 해석이 있는 두 가지 "true" 구문 모호성이 있습니다.

    • spread_element 모호 range_expression합니다. 기술적으로 다음을 가질 수 있습니다.

      Range[] ranges = [range1, ..e, range2];
      

      이 문제를 해결하기 위해 다음 중 하나를 수행할 수 있습니다.

      • 사용자가 범위를 원하는 경우 시작 인덱 (..e) 스를 0..e 괄호로 지정하거나 포함해야 합니다.
      • 스프레드에 대해 다른 구문(예: ...)을 선택합니다. 조각 패턴과의 일관성이 부족한 것은 불행한 일입니다.
  • 진정한 모호성이 없지만 구문이 구문 분석 복잡성을 크게 증가시키는 두 가지 경우가 있습니다. 엔지니어링 시간을 고려할 때 문제가 되지는 않지만 코드를 살펴볼 때 사용자에 대한 인지 오버헤드가 증가합니다.

    • 문 또는 collection_literal_expression 로컬 함수 간의 attributes 모호성 고려하다:

      [X(), Y, Z()]
      

      다음 중 하나일 수 있습니다.

      // A list literal inside some expression statement
      [X(), Y, Z()].ForEach(() => ...);
      
      // The attributes for a statement or local function
      [X(), Y, Z()] void LocalFunc() { }
      

      복잡한 lookahead가 없으면 리터럴 전체를 사용하지 않고는 알 수 없습니다.

      이 문제를 해결하는 옵션은 다음과 같습니다.

      • 이를 허용하고 구문 분석 작업을 수행하여 이러한 경우 중 어느 것인지 확인합니다.
      • 이를 허용하지 않으며 사용자가 리터럴을 다음과 같은 ([X(), Y, Z()]).ForEach(...)괄호로 래핑해야 합니다.
      • in a와 collection_literal_expressiona conditional_expressionnull_conditional_operations 사이의 모호성 고려하다:
      M(x ? [a, b, c]
      

      다음 중 하나일 수 있습니다.

      // A ternary conditional picking between two collections
      M(x ? [a, b, c] : [d, e, f]);
      
      // A null conditional safely indexing into 'x':
      M(x ? [a, b, c]);
      

      복잡한 lookahead가 없으면 리터럴 전체를 사용하지 않고는 알 수 없습니다.

      참고: 대상 입력이 을 통해 적용되기 때문에 conditional_expressions이 없는 경우에도 문제가 됩니다.

      다른 사람들과 마찬가지로, 우리는 명확하게 괄호를 요구할 수 있습니다. 즉, 다음과 null_conditional_operation같이 작성되지 않는 한 해석을 x ? ([1, 2, 3]) : 가정합니다. 그러나, 그것은 오히려 불행한 것 같다. 이러한 종류의 코드는 쓰기가 불합리해 보이지 않으며 사람들을 위로 이동시킬 가능성이 높습니다.

단점

  • 이것은 우리가 이미 가지고있는 무수한 방법 위에 컬렉션 식에 대한 또 다른 형태를 소개합니다. 이는 언어에 매우 복잡합니다. 즉, 이를 통해 하나의 구문을 통합하여 모두 지배할 수 있습니다. 즉, 기존 코드베이스를 간소화하고 모든 곳에서 균일한 모양으로 이동할 수 있습니다.
  • 대신 ...[]{ 를 사용하면 }배열 및 컬렉션 이니셜라이저에 일반적으로 사용되는 구문에서 멀리 이동합니다. 특히 []{...} 그러나 패턴을 나열할 때 언어 팀이 이미 해결했습니다. 우리는 목록 패턴을 사용하려고 {} 시도했고 극복 할 수없는 문제가 발생했습니다. 이 때문에 C[]#의 새로운 기능인 C#에서는 많은 프로그래밍 언어에서 자연스럽게 느껴지며 모호성 없이 새로 시작할 수 있게 된 것입니다. [ 해당 리터럴 형식으로 사용하는 ]것은 우리의 최신 결정과 보완이며, 우리에게 문제없이 작동 할 수있는 깨끗한 장소를 제공합니다.

이렇게 하면 언어에 wart가 도입됩니다. 예를 들어 다음 항목은 모두 합법적이며(다행히) 정확히 동일한 것을 의미합니다.

int[] x = { 1, 2, 3 };
int[] x = [ 1, 2, 3 ];

그러나 새 리터럴 구문에서 가져온 폭과 일관성을 고려할 때 사람들이 새 형식으로 이동하는 것이 좋습니다. IDE 제안 및 수정 사항은 이러한 측면에서 도움이 될 수 있습니다.

Alternatives

  • 어떤 다른 디자인이 고려되었나요? 이 작업을 수행하지 않으면 어떤 영향이 있나요?

해결된 질문

  • stackalloc을 사용할 수 없으며 반복 형식이 기본 형식인 경우 컴파일러가 스택 할당에 사용해야 하나요?

    해결 방법: 아니요. 버퍼를 stackalloc 관리하려면 컬렉션 식이 루프 내에 있을 때 버퍼가 반복적으로 할당되지 않도록 인 라인 배열 에 대한 추가 작업이 필요합니다. 컴파일러 및 생성된 코드의 복잡성이 이전 플랫폼에서 스택 할당의 이점보다 더 큽니다.

  • Length/Count 속성 평가와 비교하여 리터럴 요소를 어떤 순서로 평가해야 하나요? 먼저 모든 요소를 평가한 다음 모든 길이를 평가해야 하나요? 아니면 요소, 그 길이, 다음 요소 등을 평가해야 할까요?

    해결 방법: 먼저 모든 요소를 평가한 다음, 다른 모든 요소가 그 뒤를 따릅니다.

  • 알 수 없는 길이 리터럴이 배열, 범위 또는 Construct(array/span) 컬렉션과 같이 알려진 길이가 필요한 컬렉션 형식을 만들 수 있나요? 이 작업은 효율적으로 수행하는 것이 더 어렵지만 풀된 배열 및/또는 빌더를 영리하게 사용하면 가능할 수 있습니다.

    해결 방법: 예, 알 수 없는 길이 리터럴에서 수정 길이 컬렉션을 만들 수 있습니다. 컴파일러는 가능한 한 효율적인 방식으로 이를 구현할 수 있습니다.

    이 항목의 원래 토론을 기록하기 위해 다음 텍스트가 있습니다.

    사용자는 항상 다음과 같은 코드를 사용하여 알 수 없는 길이 리터럴을 알려진 길이 로 만들 수 있습니다.

    ImmutableArray<int> x = [a, ..unknownLength.ToArray(), b];
    

    그러나 임시 스토리지를 강제로 할당해야 하므로 이는 불행한 일입니다. 이 내보내는 방법을 제어하면 잠재적으로 더 효율적일 수 있습니다.

  • collection_expression 대상 형식을 다른 컬렉션 인터페이스에 IEnumerable<T> 지정할 수 있나요?

    다음은 그 예입니다.

    void DoWork(IEnumerable<long> values) { ... }
    // Needs to produce `longs` not `ints` for this to work.
    DoWork([1, 2, 3]);
    

    해결 방법: 예, 리터럴은 구현하는 모든 인터페이스 형식에 대상 형식 I<T>List<T> 으로 지정할 수 있습니다. 예: IEnumerable<long>. 이는 지정된 인터페이스 형식에 List<long> 대상 입력을 한 다음 해당 결과를 할당하는 것과 같습니다. 이 항목의 원래 토론을 기록하기 위해 다음 텍스트가 있습니다.

    여기서 열린 질문은 실제로 만들 기본 형식을 결정하는 것입니다. 한 가지 옵션은 에 대한 제안을 보는 것입니다 params IEnumerable<T>. 여기서는 다음과 같이 값을 전달하는 배열을 생성합니다 params T[].

  • 컴파일러가 ??에 대해 내 Array.Empty<T>() 보낼 수/있어야 합니다.[] 가능하면 할당을 피하기 위해 이 작업을 수행하도록 의무화해야 하나요?

    예. 컴파일러는 이것이 합법적이고 최종 결과가 변경할 수 없는 모든 경우에 내보내 Array.Empty<T>() 야 합니다. 예를 들어 대상 지정 T[]또는 IEnumerable<T>IReadOnlyCollection<T>IReadOnlyList<T>. 대상이 변경 가능한 경우(Array.Empty<T>또는ICollection<T>)를 사용하면 IList<T> 안 됩니다.

  • 컬렉션 이니셜라이저를 확장하여 매우 일반적인 AddRange 방법을 찾아야 하나요? 기본 생성 형식에서 스프레드 요소를 잠재적으로 더 효율적으로 추가하는 데 사용할 수 있습니다. 우리는 또한 같은 .CopyTo 것들을 찾고 싶을 수도 있습니다. 이러한 메서드로 인해 변환된 코드에서 직접 열거하는 것과 비교하면 초과 할당/디스패치를 유발할 수 있으므로 여기에 단점이 있을 수 있습니다.

    예. 구현은 이러한 메서드에 잘 정의된 의미 체계가 있고 컬렉션 형식이 "잘 동작"되어야 한다는 가정 하에 컬렉션 값을 초기화하는 다른 메서드를 활용할 수 있습니다. 그러나 실제로 구현은 한 가지 방법(대량 복사)의 이점도 부정적인 결과를 초래할 수 있으므로 주의해야 합니다(예: 구조체 컬렉션 boxing).

    구현은 단점이 없는 경우를 활용해야 합니다. 예를 들어 메서드를 사용합니다 .AddRange(ReadOnlySpan<T>) .

해결되지 않은 질문

  • 반복 형식이 "모호한"일 때 요소 형식을 유추하도록 허용해야 하나요? 다음은 그 예입니다.
Collection x = [1L, 2L];

// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new Collection) { }

static class Builder
{
    public Collection Create(ReadOnlySpan<long> items) => throw null;
}

[CollectionBuilder(...)]
class Collection : IEnumerable<int>, IEnumerable<string>
{
    IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw null;
    IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}
  • 컬렉션 리터럴을 만들고 즉시 인덱싱하는 것이 합법적이어야 하나요? 참고: 컬렉션 리터럴에 자연 형식이 있는지 여부에 대한 아래의 해결되지 않은 질문에 대한 답변이 필요합니다.

  • 거대한 컬렉션에 대한 스택 할당은 스택을 날려 버릴 수 있습니다. 컴파일러에 이 데이터를 힙에 배치하기 위한 추론이 있어야 하나요? 이러한 유연성을 허용하도록 언어를 지정하지 않아야 하나요? 에 대한 params Span<T>사양을 따라야 합니다.

  • 대상 유형이 필요한가요 spread_element? 예를 들어 다음을 고려합니다.

    Span<int> span = [a, ..b ? [c] : [d, e], f];
    

    참고: 이는 일반적으로 일부 요소 집합의 조건부 포함을 허용하기 위해 다음 형식으로 표시되거나 조건이 false인 경우 아무 것도 허용하지 않을 수 있습니다.

    Span<int> span = [a, ..b ? [c, d, e] : [], f];
    

    이 전체 리터럴을 평가하려면 내부의 요소 식을 평가해야 합니다. 즉, 평가할 b ? [c] : [d, e]수 있습니다. 그러나 컨텍스트에서 이 식을 평가할 대상 형식이 없고 모든 종류의 자연 형식이 없으면 여기서 수행할 작업을 [c][d, e] 결정할 수 없습니다.

    이 문제를 해결하기 위해 리터럴의 식을 평가할 때 리터럴 자체의 spread_element 대상 형식에 해당하는 암시적 대상 형식이 있다고 말할 수 있습니다. 따라서 위에서는 다음과 같이 다시 작성됩니다.

    int __e1 = a;
    Span<int> __s1 = b ? [c] : [d, e];
    int __e2 = f;
    
    Span<int> __result = stackalloc int[2 + __s1.Length];
    int __index = 0;
    
    __result[__index++] = a;
    foreach (int __t in __s1)
      __result[index++] = __t;
    __result[__index++] = f;
    
    Span<int> span = __result;
    

create 메서드를 활용하는 생성 가능한 컬렉션 형식의 사양은 변환이 분류되는 컨텍스트에 민감합니다.

이 경우 변환의 존재는 컬렉션 형식반복 형식 개념에 따라 달라집니다. 반복 형식이 있는 위치를 ReadOnlySpan<T> 사용하는 Tcreate 메서드가 있는 경우 변환이 존재합니다. 그렇지 않으면 그렇지 않습니다.

그러나 반복 형식 은 수행되는 foreach 컨텍스트에 민감합니다. 동일한 컬렉션 형식 의 경우 범위에 있는 확장 메서드에 따라 다를 수 있으며 정의되지 않을 수도 있습니다.

형식 자체가 foreach-able이 되도록 설계되지 않은 경우의 foreach 목적을 위해 괜찮다고 느낍니다. 이 경우 확장 메서드는 컨텍스트에 관계없이 형식이 foreach-ed 오버되는 방식을 변경할 수 없습니다.

그러나 변환이 컨텍스트에 민감하기 때문에 다소 이상하게 느껴집니다. 실제로 변환은 "불안정"합니다. 생성 가능하도록 명시적으로 설계된 컬렉션 형식은 매우 중요한 세부 정보인 반복 형식의 정의를 제외할 수 있습니다. 형식을 "변환할 수 없음"으로 그대로 둡니다.

예를 들어 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
class MyCollection
{
}
class MyCollectionBuilder
{
    public static MyCollection Create(ReadOnlySpan<long> items) => throw null;
    public static MyCollection Create(ReadOnlySpan<string> items) => throw null;
}

namespace Ns1
{
    static class Ext
    {
        public static IEnumerator<long> GetEnumerator(this MyCollection x) => throw null;
    }
    
    class Program
    {
        static void Main()
        {
            foreach (var l in new MyCollection())
            {
                long s = l;
            }
        
            MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
                               2];
        }
    }
}

namespace Ns2
{
    static class Ext
    {
        public static IEnumerator<string> GetEnumerator(this MyCollection x) => throw null;
    }
    
    class Program
    {
        static void Main()
        {
            foreach (var l in new MyCollection())
            {
                string s = l;
            }
        
            MyCollection x1 = ["a",
                               2]; // error CS0029: Cannot implicitly convert type 'int' to 'string'
        }
    }
}

namespace Ns3
{
    class Program
    {
        static void Main()
        {
            // error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator'
            foreach (var l in new MyCollection())
            {
            }
        
            MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type.
        }
    }
}

현재 디자인에서 형식이 반복 형식 자체를 정의하지 않는 경우 컴파일러는 특성 애플리케이션의 유효성을 안정적으로 CollectionBuilder 검사할 수 없습니다. 반복 형식을 모르는 경우 create 메서드의 서명이 무엇인지 알 수 없습니다. 반복 형식이 컨텍스트에서 가져온 경우 형식이 항상 유사한 컨텍스트에서 사용된다는 보장은 없습니다.

Params 컬렉션 기능도 영향을 받습니다. 선언 지점에서 매개 변수의 요소 형식을 안정적으로 예측할 수 없는 것이 params 이상하게 느껴집니다. 또한 현재 제안에서는 create 메서드params만큼 액세스할 수 있는지 확인해야 합니다. 컬렉션 형식반복 형식 자체를 정의하지 않는 한 신뢰할 수 있는 방식으로 이 검사를 수행할 수 없습니다.

기본적으로 동일한 문제를 관찰하지만 최적화의 관점에서 논의하는 컴파일러에 대해서도 https://github.com/dotnet/roslyn/issues/69676 열었습니다.

제안

자체적으로 반복 형식을 CollectionBuilder 정의하려면 특성을 활용하는 형식 이 필요합니다. 즉, 형식이 구현 IEnumarable/IEnumerable<T>되거나 올바른 서명이 있는 public GetEnumerator 메서드가 있어야 합니다(확장 메서드는 제외됨).

또한 지금 만들기 메서드 는 "컬렉션 식이 사용되는 위치에 액세스할 수 있어야 합니다". 접근성을 기반으로 하는 또 다른 컨텍스트 종속성 지점입니다. 이 메서드의 목적은 사용자 정의 변환 메서드의 목적과 매우 유사하며 공용이어야 합니다. 따라서 create 메서드 도 공개하도록 요구하는 것이 좋습니다.

결론

LDM-2024-01-08 수정으로 승인됨

반복 형식의 개념은 변환 전체에서 일관되게 적용되지 않습니다.

  • 다음 위치를 구현하는 구조체 또는 클래스 형식에 대해 다음을 수행합니다.System.Collections.Generic.IEnumerable<T>
    • 요소Ei 에 대해 .로 의 암시적 변환 이 있습니다 T.

이 경우 T 또는 클래스 형식반복 형식이 필요하다고 가정 하는 것처럼 보입니다. 그러나 해당 가정은 올바르지 않습니다. 이는 매우 이상한 동작으로 이어질 수 있습니다. 다음은 그 예입니다.

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

class MyCollection : IEnumerable<long>
{
    IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public void Add(string l) => throw null;
    
    public IEnumerator<string> GetEnumerator() => throw null; 
}

class Program
{
    static void Main()
    {
        foreach (var l in new MyCollection())
        {
            string s = l; // Iteration type is string
        }
        
        MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
                           2];
        MyCollection x2 = new MyCollection() { "b" };
    }
}
  • 구현하고 구현하지 않는System.Collections.IEnumerable 또는 클래스 형식입니다System.Collections.Generic.IEnumerable<T>.

구현에서는 반복 형식 이 있다고 가정하지만 사양에 object따라 이 팩트가 지정되지 않고 각 요소가 아무것도 변환할 필요가 없습니다. 그러나 일반적으로 반복 형식 은 형식이 object 필요하지 않습니다. 다음 예제에서 관찰할 수 있는 것은 다음과 같습니다.

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

class MyCollection : IEnumerable
{
    public IEnumerator<string> GetEnumerator() => throw null; 
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

class Program
{
    static void Main()
    {
        foreach (var l in new MyCollection())
        {
            string s = l; // Iteration type is string
        }
    }
}

반복 형식의 개념은 Params 컬렉션 기능의 기본 요소입니다. 그리고이 문제는 두 기능 사이의 이상한 불일치로 이어집니다. 예를 들어:

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

class MyCollection : IEnumerable<long>
{
    IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public IEnumerator<string> GetEnumerator() => throw null; 

    public void Add(long l) => throw null; 
    public void Add(string l) => throw null; 
}

class Program
{
    static void Main()
    {
        Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long'
        Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string'
        Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string'
        Test([3]); // Ok

        MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long'
        MyCollection x2 = [3];
    }

    static void Test(params MyCollection a)
    {
    }
}
using System.Collections;
using System.Collections.Generic;

class MyCollection : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() => throw null;

    public IEnumerator<string> GetEnumerator() => throw null; 
    public void Add(object l) => throw null;
}

class Program
{
    static void Main()
    {
        Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string'
        Test(["2", 3]); // Ok
    }

    static void Test(params MyCollection a)
    {
    }
}

그것은 아마 한 가지 방법 또는 다른 정렬 하는 것이 좋을 것 이다.

제안

반복 형식을 구현 하거나 기준으로 각 System.Collections.Generic.IEnumerable<T>System.Collections.IEnumerable반복 형식으로 암시적으로 변환해야 하는 구조체 또는 Ei 클래스 형식의 변환 가능성을 지정합니다.

결론

승인된 LDM-2024-01-08

컬렉션 식 변환을 사용하려면 최소 API 집합을 생성할 수 있어야 하나요?

변환에 따른 생성 가능한 컬렉션 형식은 실제로 생성할 수 없으므로 예기치 않은 오버로드 확인 동작이 발생할 수 있습니다. 다음은 그 예입니다.

class C1
{
    public static void M1(string x)
    {
    }
    public static void M1(char[] x)
    {
    }
    
    void Test()
    {
        M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])'
    }
}

그러나 'C1. M1(string)'은 다음과 같은 이유로 사용할 수 있는 후보가 아닙니다.

error CS1729: 'string' does not contain a constructor that takes 0 arguments
error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)

다음은 사용자 정의 형식과 유효한 후보를 언급하지 않는 더 강력한 오류가 있는 또 다른 예입니다.

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

class C1 : IEnumerable<char>
{
    public static void M1(C1 x)
    {
    }
    public static void M1(char[] x)
    {
    }

    void Test()
    {
        M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
    }

    public static implicit operator char[](C1 x) => throw null;
    IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

이 상황은 변환을 위임하기 위해 메서드 그룹과 함께 사용했던 것과 매우 유사합니다. 즉, 변환이 존재하지만 잘못된 시나리오가 있었습니다. 변환이 잘못되면 존재하지 않도록 하여 이를 개선하기로 결정했습니다.

"Params 컬렉션" 기능을 사용하면 비슷한 문제가 발생합니다. 생성할 수 없는 컬렉션에 대해 한정자 사용을 params 허용하지 않는 것이 좋을 수 있습니다. 그러나 현재 제안에서 확인은 변환 섹션을 기반으로 합니다 . 예를 들어 다음과 같습니다.

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

class C1 : IEnumerable<char>
{
    public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier
    {
    }
    public static void M1(params ushort[] x)
    {
    }

    void Test()
    {
        M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
        M2('a', 'b'); // Ok
    }

    public static void M2(params ushort[] x)
    {
    }

    IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
    IEnumerator IEnumerable.GetEnumerator() => throw null;
}

문제가 이전에 다소 논의된 것 같습니다. 참조하세요 https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. 당시에는 현재 지정한 대로 규칙이 보간된 문자열 처리기를 지정하는 방법과 일치한다는 인수가 만들어졌습니다. 다음은 따옴표입니다.

특히 보간된 문자열 처리기는 원래 이러한 방식으로 지정되었지만 이 문제를 고려한 후 사양을 수정했습니다.

몇 가지 유사성이 있지만 고려해야 할 중요한 차이점도 있습니다. 다음은 따옴표입니다.https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion

형식 T특성이 있는 경우 System.Runtime.CompilerServices.InterpolatedStringHandlerAttributeapplicable_interpolated_string_handler_type 합니다. interpolated_string_expression 암시적 T 존재하거나 _interpolated_string_expression_s 전적으로 구성되고 연산자만 + 사용하는 additive_expression 있습니다.

대상 형식에는 보간된 문자열 처리기가 될 형식에 대한 작성자의 의도를 강력하게 나타내는 특수 특성이 있어야 합니다. 특성의 존재가 우연이 아니라고 가정하는 것이 공평합니다. 반면 형식이 "열거 가능"하다는 사실은 생성 가능한 형식에 대한 작성자의 의도가 있음을 의미하지는 않습니다. 그러나 컬렉션 형식의 특성으로 표시되는 [CollectionBuilder(...)]의 존재는 생성 가능한 형식에 대한 작성자의 의도를 강력하게 나타내는 것처럼 느껴집니다.

제안

메서드 만들기System.Collections.IEnumerable 섹션을 구현 하고 없는 구조체 또는 클래스 형식의 경우 다음 API 이상이 있어야 합니다.

  • 인수 없이 적용할 수 있는 액세스 가능한 생성자입니다.
  • Add의 값을 인수로 사용하여 호출할 수 있는 액세스 가능한 인스턴스 또는 확장 메서드입니다.

Params Collectons 기능을 위해 이러한 API가 공용으로 선언되고 인스턴스(확장과 비교) 메서드인 경우 이러한 형식은 유효한 params 형식입니다.

결론

LDM-2024-01-10 수정으로 승인됨