인라인 배열
메모
이 문서는 기능 사양입니다. 사양은 기능의 디자인 문서 역할을 합니다. 여기에는 기능 디자인 및 개발 중에 필요한 정보와 함께 제안된 사양 변경 내용이 포함됩니다. 이러한 문서는 제안된 사양 변경이 완료되고 현재 ECMA 사양에 통합될 때까지 게시됩니다.
기능 사양과 완료된 구현 간에 약간의 불일치가 있을 수 있습니다. 이러한 차이는 관련된 언어 디자인 모임(LDM) 노트에 기록됩니다.
사양문서에서 C# 언어 표준에 기능 스펙릿을 채택하는 프로세스에 대해 자세히 알아볼 수 있습니다.
요약
InlineArrayAttribute 기능을 활용하여 구조체 형식을 활용하는 범용적이고 안전한 메커니즘을 제공합니다. C# 클래스, 구조체 및 인터페이스 내에서 인라인 배열을 선언하기 위한 범용 및 안전한 메커니즘을 제공합니다.
참고: 이 사양의 이전 버전에서는 Span 안전성 기능 사양에 도입된 "ref-safe-to-escape" 및 "safe-to-escape"라는 용어를 사용했습니다.
ECMA 표준 위원회는 이름을 "ref-safe-context""안전 컨텍스트" 각각 변경했습니다. 안전 컨텍스트의 값은 "declaration-block", "function-member" 및 "caller-context"를 일관되게 사용하도록 구체화되었습니다. 스펙렛들은 이러한 용어에 대해 다른 표현을 사용했으며 "호출자 컨텍스트"의 동의어로 "반환 안전(safe-to-return)"을 사용했습니다. 이 스펙렛은 C# 7.3 표준의 용어를 사용하도록 업데이트되었습니다.
동기
이 제안은 https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers의 많은 제한 사항들을 해결할 계획이다. 특히 다음을 허용하는 것을 목표로 합니다.
- InlineArrayAttribute 기능을 활용하여 구조체 형식의 요소에 액세스
-
struct
,class
또는interface
관리되는 형식과 관리되지 않는 형식에 대한 인라인 배열 선언입니다.
그리고 그들을 위해 언어 안전 확인을 제공합니다.
상세 디자인
최근에 런타임에 InlineArrayAttribute 기능이 추가되었습니다. 즉, 사용자는 다음과 같은 구조체 형식을 선언할 수 있습니다.
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
런타임은 Buffer
형식에 대한 특수 형식 레이아웃을 제공합니다.
- 형식의 크기는
object
형식의 요소 10개(숫자 10은 InlineArray 속성에서 가져옴)에 맞추어 확장됩니다. 여기서 형식은 구조체 내 유일한 인스턴스 필드의 형식에서 가져온 것으로, 이 예시에서는_element0
입니다. - 첫 번째 요소는 인스턴스 필드 및 구조체의 시작 부분에 맞춰집니다.
- 요소는 배열의 요소인 것처럼 메모리에 순차적으로 배치됩니다.
런타임은 구조체의 모든 요소에 대한 정기적인 GC 추적을 제공합니다.
이 제안은 이와 같은 형식을 "인라인 배열 형식"으로 참조합니다.
인라인 배열 형식의 요소는 포인터를 통해 또는 System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan API에서 반환된 범위 인스턴스를 통해 액세스할 수 있습니다. 그러나 포인터 접근 방식이나 API는 기본적으로 형식 및 경계 검사를 제공하지 않습니다.
언어는 인라인 배열 형식의 요소에 접근하는 형식 및 참조 안전한 방법을 제공합니다. 액세스는 범위에 따라 결정됩니다. 지원은 형식 인수로 사용할 수 있는 요소 형식을 가진 인라인 배열 형식으로 제한됩니다. 예를 들어 포인터 형식은 요소 형식으로 사용할 수 없습니다. 다른 예는 범위 형식입니다.
인라인 배열 형식에 대한 범위 형식의 인스턴스 가져오기
인라인 배열 형식의 첫 번째 요소가 형식의 시작 부분(간격 없음)에 맞춰지도록 보장되므로 컴파일러는 다음 코드를 사용하여 Span
값을 가져옵니다.
MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
ReadOnlySpan
값을 가져오는 다음 코드는 다음과 같습니다.
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
사용 사이트 컴파일러에서 IL 크기를 줄이려면 두 개의 일반 재사용 가능한 도우미를 프라이빗 구현 세부 정보 유형에 추가하고 동일한 프로그램의 모든 사용 사이트에서 사용할 수 있어야 합니다.
public static System.Span<TElement> InlineArrayAsSpan<TBuffer, TElement>(ref TBuffer buffer, int size) where TBuffer : struct
{
return MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
}
public static System.ReadOnlySpan<TElement> InlineArrayAsReadOnlySpan<TBuffer, TElement>(in TBuffer buffer, int size) where TBuffer : struct
{
return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
}
요소 접근
요소 액세스 인라인 배열 요소 액세스를 지원하도록 확장됩니다.
element_access은 primary_no_array_creation_expression뒤에 "[
" 토큰이 오고, 그 뒤로 argument_list, 그리고 마지막으로 "]
" 토큰이 이어지는 형태로 구성됩니다.
argument_list는 쉼표로 구분된 하나 이상의 인수로 구성됩니다.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
element_access의 argument_list는 ref
또는 out
인수를 포함할 수 없습니다.
다음 중 하나 이상이 있는 경우 element_access 동적으로 바인딩됩니다(§11.3.3).
-
primary_no_array_creation_expression은 컴파일 타임 타입입니다
dynamic
. -
argument_list에 포함된 하나 이상의 식이 컴파일 시간 형식
dynamic
을 가지며, primary_no_array_creation_expression는 배열 형식이 아니고, primary_no_array_creation_expression는 인라인 배열 형식이 아니거나 인수 목록에 둘 이상의 항목이 있습니다.
이 경우 컴파일러는 element_accessdynamic
형식의 값으로 분류합니다.
element_access 의미를 결정하는 아래 규칙은 컴파일 시간 형식이 dynamic
primary_no_array_creation_expression 및 argument_list 식의 컴파일 시간 형식 대신 런타임 형식을 사용하여 런타임에 적용됩니다.
primary_no_array_creation_expression이(가) 컴파일 시 타입 dynamic
이(가) 없는 경우, 요소 액세스는 §11.6.5에 설명된 대로 제한된 컴파일 시 검사를 거칩니다.
primary_no_array_creation_expression이 element_access의 array_type값인 경우, element_access는 배열 액세스(§12.8.12.2)입니다. element_accessprimary_no_array_creation_expression 인라인 배열 형식의 변수 또는 값이고 argument_list 단일 인수로 구성된 경우 element_access 인라인 배열 요소 액세스입니다. 그렇지 않으면 primary_no_array_creation_expression은(는) 하나 이상의 인덱서 멤버가 있는 클래스, 구조체, 또는 인터페이스 형식의 변수 또는 값이어야 하며, 이때 element_access은(는) 인덱서 액세스(§12.8.12.3)입니다.
인라인 배열 요소 액세스
인라인 배열 요소 엑세스를 위해서는 element_access의 primary_no_array_creation_expression이 인라인 배열 형식의 변수 또는 값이어야 합니다. 또한 인라인 배열 요소 액세스의 argument_list 명명된 인수를 포함할 수 없습니다. argument_list에는 단일 식이 포함되어야 하며, 그 식은 유효해야 합니다.
- 형식
int
의 경우 또는 -
int
으로 암시적으로 변환 가능 또는 -
System.Index
으로 암시적으로 변환 가능 또는 -
System.Range
으로 암시적으로 변환할 수 있습니다.
식 형식이 int인 경우
primary_no_array_creation_expression이 쓰기 가능한 변수인 경우, 인라인 배열 요소 액세스를 평가한 결과는 primary_no_array_creation_expression의 System.Span<T> InlineArrayAsSpan
메서드에서 반환된 System.Span<T>
인스턴스에 해당 정수 값을 사용하여 public ref T this[int index] { get; }
를 호출한 것과 동일한 쓰기 가능한 변수입니다. ref-safety 분석을 위해, 액세스의 ref-safe-context/안전 컨텍스트는 서명 static ref T GetItem(ref InlineArrayType array)
를 가진 메서드를 호출할 때의 안전 컨텍스트와 동일합니다.
primary_no_array_creation_expression이 이동 가능할 경우에만 결과 변수가 이동 가능한 것으로 간주됩니다.
primary_no_array_creation_expression이 읽기 전용 변수인 경우, 인라인 배열 요소 액세스를 평가한 결과는 primary_no_array_creation_expression의 System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
메서드를 통해 반환된 System.ReadOnlySpan<T>
인스턴스에서 해당 정수 값으로 public ref readonly T this[int index] { get; }
를 호출하는 것과 동일한 읽기 전용 변수입니다. ref-safety 분석을 위해 액세스의 참조 안전 컨텍스트/안전 컨텍스트는 서명 static ref readonly T GetItem(in InlineArrayType array)
을(를) 가진 메서드를 호출하는 것과 동등합니다.
결과 변수는 primary_no_array_creation_expression이 이동 가능한 경우에 한하여 이동 가능한 것으로 간주됩니다.
primary_no_array_creation_expression 값인 경우, 인라인 배열 요소 액세스를 평가한 결과는 primary_no_array_creation_expression에서 System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
메서드로 반환된 System.ReadOnlySpan<T>
인스턴스에 해당 정수 값을 사용하여 public ref readonly T this[int index] { get; }
를 호출한 것과 동일한 값입니다. ref-safety 분석을 위해, 액세스의 ref-safe-context/안전 컨텍스트는 서명 static T GetItem(InlineArrayType array)
를 가진 메서드를 호출하는 것과 동일하게 취급됩니다.
예를 들어:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
}
void M1(Buffer10<int> x)
{
ref int a = ref x[0]; // Ok, equivalent to `ref int a = ref InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10)[0]`
}
void M2(in Buffer10<int> x)
{
ref readonly int a = ref x[0]; // Ok, equivalent to `ref readonly int a = ref InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)[0]`
ref int b = ref x[0]; // An error, `x` is a readonly variable => `x[0]` is a readonly variable
}
Buffer10<int> GetBuffer() => default;
void M3()
{
int a = GetBuffer()[0]; // Ok, equivalent to `int a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(GetBuffer(), 10)[0]`
ref readonly int b = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value
ref int c = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value
}
선언된 인라인 배열 범위를 벗어난 상수 식을 사용하여 인라인 배열로 인덱싱하는 것은 컴파일 시간 오류입니다.
표현식이 암시적으로 int
로 변환될 수 있는 경우
식이 int로 변환된 다음, 식의 형식이 int일 때 섹션에 설명된 대로 요소 접근이 해석됩니다.
식이 암시적으로 System.Index
로 변환될 수 있는 경우
식은 System.Index
로 변환되고, 이는 https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support에서 설명한 대로 int 기반 인덱스 값으로 다시 변환됩니다. 이 과정은 컴파일 시점에 컬렉션의 길이가 알려져 있고, primary_no_array_creation_expression인라인 배열 유형의 요소 수와 동일하다고 가정합니다. 그런 다음, 식 형식이 int 섹션인 경우
표현식이 암시적으로 System.Range
로 변환할 수 있는 경우
primary_no_array_creation_expression이 변수로 작성할 수 있는 경우, 인라인 배열 요소 액세스를 평가한 결과는 primary_no_array_creation_expression의 System.Span<T> InlineArrayAsSpan
메서드에서 반환된 System.Span<T>
인스턴스에 public Span<T> Slice (int start, int length)
를 호출하는 것과 동일한 값입니다.
ref-safety 분석을 위해 액세스의 ref-safe-context/안전 컨텍스트는 서명이 static System.Span<T> GetSlice(ref InlineArrayType array)
인 메서드를 호출하는 경우와 동일한 것으로 간주됩니다.
primary_no_array_creation_expression이 읽기 전용 변수인 경우, 인라인 배열 요소 액세스를 평가한 결과는 primary_no_array_creation_expression의 System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
메서드에서 반환된 System.ReadOnlySpan<T>
인스턴스에서 public ReadOnlySpan<T> Slice (int start, int length)
를 호출하는 것과 동일한 값입니다.
ref-safety 분석을 위해, 액세스의 ref-safe-context/와 안전 컨텍스트는 서명 static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array)
를 가진 메서드를 호출할 때와 동일합니다.
primary_no_array_creation_expression 값이면 오류가 발생한다고 보고됩니다.
Slice
메서드 호출에 대한 인수는 https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support에 설명된 대로 System.Range
으로 변환된 인덱스 식을 기반으로 계산됩니다. 컬렉션의 길이는 컴파일 시점에 이미 알려져 있으며, primary_no_array_creation_expression의 인라인 배열 형식의 요소 수와 동일하다고 가정합니다.
컴파일러는 컴파일 시간에 start
0이고 length
인라인 배열 형식의 요소 양보다 작거나 같은 것으로 알려진 경우 Slice
호출을 생략할 수 있습니다. 컴파일러는 컴파일 시 조각화가 인라인 배열 범위를 벗어난 것으로 알려진 경우에도 오류를 보고할 수 있습니다.
예를 들어:
void M1(Buffer10<int> x)
{
System.Span<int> a = x[..]; // Ok, equivalent to `System.Span<int> a = InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10).Slice(0, 10)`
}
void M2(in Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x[..]; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10).Slice(0, 10)`
System.Span<int> b = x[..]; // An error, System.ReadOnlySpan<int> cannot be converted to System.Span<int>
}
Buffer10<int> GetBuffer() => default;
void M3()
{
_ = GetBuffer()[..]; // An error, `GetBuffer()` is a value
}
변환
식에서 새 변환인 인라인 배열 변환이 추가됩니다. 직렬 배열 변환은 표준 변환입니다.
인라인 배열 형식의 식에서 다음 형식으로의 암시적 변환이 있습니다.
System.Span<T>
System.ReadOnlySpan<T>
그러나 읽기 전용 변수를 System.Span<T>
변환하거나 값을 두 형식으로 변환하는 것은 오류입니다.
예를 들어:
void M1(Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)`
System.Span<int> b = x; // Ok, equivalent to `System.Span<int> b = InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10)`
}
void M2(in Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)`
System.Span<int> b = x; // An error, readonly mismatch
}
Buffer10<int> GetBuffer() => default;
void M3()
{
System.ReadOnlySpan<int> a = GetBuffer(); // An error, ref-safety
System.Span<int> b = GetBuffer(); // An error, ref-safety
}
ref-safety 분석을 위해, 변환의 안전 컨텍스트는 서명 static System.Span<T> Convert(ref InlineArrayType array)
또는 static System.ReadOnlySpan<T> Convert(in InlineArrayType array)
가 있는 메서드를 호출할 때의 안전 컨텍스트와 동일합니다.
목록 패턴
인라인 배열 형식의 인스턴스에는 목록 패턴이 지원되지 않습니다.
명확한 과제 확인
정기적인 명확한 할당 규칙은 인라인 배열 형식이 있는 변수에 적용할 수 있습니다.
컬렉션 리터럴
인라인 배열 형식의 인스턴스는 spread_element에서 유효한 식입니다.
다음 기능은 C# 12에서 제공하지 않았습니다. 공개 제안으로 남아있다. 이 예제의 코드는 CS9174을 생성합니다.
인라인 배열 형식은 컬렉션 식에 대한 유효한 생성 가능한 컬렉션 대상 형식입니다. 예를 들어:
Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array
컬렉션 리터럴의 길이는 대상 인라인 배열 형식의 길이와 일치해야 합니다. 리터럴의 길이가 컴파일 시간에 알려져 있고 대상 길이와 일치하지 않으면 오류가 보고됩니다. 그렇지 않으면 불일치가 발견되면 실행 시간에 예외가 발생합니다. 정확한 예외 유형은 TBD입니다. 일부 후보는 System.NotSupportedException, System.InvalidOperationException입니다.
InlineArrayAttribute 애플리케이션의 유효성 검사
컴파일러는 InlineArrayAttribute 애플리케이션의 다음 측면의 유효성을 검사합니다.
개체 이니셜라이저의 인라인 배열 요소
기본적으로 양식 '[' argument_list ']'
initializer_target 통해 요소 초기화가 지원되지 않습니다(https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128173-object-initializers참조).
static C M2() => new C() { F = {[0] = 111} }; // error CS1913: Member '[0]' cannot be initialized. It is not a field or property.
class C
{
public Buffer10<int> F;
}
그러나 인라인 배열 형식에 적절한 인덱서가 명시적으로 정의되어 있으면, 개체 초기화 과정에서 이를 사용합니다.
static C M2() => new C() { F = {[0] = 111} }; // Ok, indexer is invoked
class C
{
public Buffer10<int> F;
}
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
public T this[int i]
{
get => this[i];
set => this[i] = value;
}
}
foreach 문
foreach 문 에서 인라인 배열 형식을 컬렉션으로 사용할 수 있도록 기능이 변경됩니다.
예를 들어:
foreach (var a in getBufferAsValue())
{
WriteLine(a);
}
foreach (var b in getBufferAsWritableVariable())
{
WriteLine(b);
}
foreach (var c in getBufferAsReadonlyVariable())
{
WriteLine(c);
}
Buffer10<int> getBufferAsValue() => default;
ref Buffer10<int> getBufferAsWritableVariable() => default;
ref readonly Buffer10<int> getBufferAsReadonlyVariable() => default;
에 해당합니다.
Buffer10<int> temp = getBufferAsValue();
foreach (var a in (System.ReadOnlySpan<int>)temp)
{
WriteLine(a);
}
foreach (var b in (System.Span<int>)getBufferAsWritableVariable())
{
WriteLine(b);
}
foreach (var c in (System.ReadOnlySpan<int>)getBufferAsReadonlyVariable())
{
WriteLine(c);
}
우리는 스팬 타입이 번역에 관여하여 async
메서드에서 처음에는 제한될지라도 인라인 배열을 통해 foreach
을 지원할 것입니다.
디자인 질문 열기
대안
인라인 배열 형식 구문
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general 문법은 다음과 같이 조정됩니다.
array_type
: non_array_type rank_specifier+
;
rank_specifier
: '[' ','* ']'
+ | '[' constant_expression ']'
;
constant_expression 형식은 int
형식으로 암시적으로 변환할 수 있어야 하며 값은 0이 아닌 양의 정수여야 합니다.
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general 섹션의 관련 부분은 다음과 같이 조정됩니다.
배열 형식에 대한 문법 프로덕션은 https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general에서 제공되어 있습니다.
배열 형식은 non_array_type 다음에 하나 이상의 rank_specifier가 오는 형태로 기술됩니다.
non_array_type는 스스로 array_type가 아닌 모든 형식입니다.
배열 형식의 순위는 rank_specifier 가장 왼쪽에 있는 array_type에 의해 정해집니다. rank_specifier는 배열이 rank_specifier의 “,
” 토큰 수에 1을 더한 순위를 갖고 있음을 나타냅니다.
배열 형식의 요소 형식은 가장 왼쪽 rank_specifier를 삭제했을 때 얻어지는 형식입니다.
-
T[ constant_expression ]
양식의 배열 형식은 constant_expression에 의해 길이가 표시되는 익명 인라인 배열 형식이며, 배열이 아닌 요소 형식은T
입니다. -
T[ constant_expression ][R₁]...[Rₓ]
형식의 배열 형식은 길이가 constant_expression에 의해 지정되고, 요소 형식이T[R₁]...[Rₓ]
인 익명의 인라인 배열 형식입니다. - 형식
T[R]
(여기서 R이 constant_expression가 아닌 경우)은 순위가R
이고 배열이 아닌 요소 형식이T
인 일반 배열 형식입니다. - 형식
T[R][R₁]...[Rₓ]
의 배열 유형(R이 상수_표현식이 아닌 경우)은 순위가R
이고 요소 유형이T[R₁]...[Rₓ]
인 일반 배열 유형입니다.
실제로
예제:
int[][,,][,]
형식은int
의 2차원 배열로 이루어진 3차원 배열의 1차원 배열입니다. 예제의 끝
런타임에 일반 배열 형식의 값은 null
또는 해당 배열 형식의 인스턴스에 대한 참조일 수 있습니다.
참고: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariance규칙에 따라 값은 공변 배열 형식에 대한 참조일 수도 있습니다. 끝 메모
익명 인라인 배열 형식은 내부 접근성이 있는 인라인 배열 형식을 합성한 컴파일러입니다. 요소 형식은 형식 인수로 사용할 수 있는 형식이어야 합니다. 명시적으로 선언된 인라인 배열 형식과 달리 익명 인라인 배열 형식은 이름으로 참조할 수 없으며 array_type 구문에서만 참조할 수 있습니다. 동일한 프로그램의 컨텍스트에서 동일한 요소 형식과 동일한 길이를 가지는 인라인 배열 형식을 나타내는 두 개의 array_type는 동일한 익명 인라인 배열 형식을 참조합니다.
내부 접근성 외에도 컴파일러는 서명에서 익명 인라인 배열 형식 참조에 요구되는 사용자 정의 수정자(정확한 형식 TBD)를 적용하여 어셈블리 경계를 넘나드는 익명 인라인 배열 형식을 활용하는 API 사용을 방지할 것입니다.
배열 만들기 식
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
현재 문법을 고려할 때 constant_expression을 expression_list 대신 사용함으로써 지정된 길이의 1차원 배열 형태를 할당하는 의미가 내포되어 있습니다. 따라서 array_creation_expression은 일반 배열의 할당을 계속해서 나타낼 것입니다.
그러나 rank_specifier 새 형식을 사용하여 익명 인라인 배열 형식을 할당된 배열의 요소 형식에 통합할 수 있습니다.
예를 들어 다음 식은 요소 형식 int 및 길이가 5인 무명 인라인 배열 형식의 요소 형식을 사용하여 길이 2의 정규 배열을 만듭니다.
new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};
배열 이니셜라이저
배열 이니셜라이저는 C# 12에서 구현되지 않았습니다. 이 섹션은 활성 제안으로 남아 있습니다.
배열 이니셜라이저 섹션은 array_initializer 사용하여 인라인 배열 형식을 초기화할 수 있도록 조정됩니다(문법을 변경할 필요가 없음).
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
인라인 배열의 길이는 대상 형식에서 명시적으로 제공해야 합니다.
예를 들어:
int[5] a = {1, 2, 3, 4, 5}; // initializes anonymous inline array of length 5
Buffer10<int> b = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // initializes user-defined inline array
var c = new int[][] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer
var d = new int[][2] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer
상세 디자인(옵션 2)
이 제안의 목적을 위해 "고정 크기 버퍼"라는 용어는 https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers에 설명된 버퍼가 아닌 제안된 "안전한 고정 크기 버퍼" 기능을 의미합니다.
이 디자인에서 고정 크기 버퍼 형식은 언어에 의한 일반적인 특수 처리를 받지 않습니다. 고정 크기 버퍼를 나타내는 멤버를 선언하는 특수 구문과 해당 멤버 사용과 관련된 새 규칙이 있습니다. 언어 관점에서 필드가 아닙니다.
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields에서 variable_declarator의 문법은 버퍼의 크기를 지정할 수 있도록 확장됩니다.
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| unsafe_modifier // unsafe code support
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
+ | fixed_size_buffer_declarator
;
fixed_size_buffer_declarator
: identifier '[' constant_expression ']'
;
fixed_size_buffer_declarator 지정된 요소 형식의 고정 크기 버퍼를 선언합니다.
버퍼 요소 유형은 field_declaration
에서 지정된 유형입니다. 고정 크기 버퍼 선언자는 새 멤버를 도입하고 멤버 이름을 지정하는 식별자로 구성되며 그 다음에는 [
및 ]
토큰으로 묶인 상수 식이 있습니다. 상수 식은 해당 고정 크기 버퍼 선언자가 도입한 멤버의 요소 수를 표시합니다. 상수 식의 형식은 int
형식으로 암시적으로 변환할 수 있어야 하며 값은 0이 아닌 양의 정수여야 합니다.
고정 크기 버퍼의 요소는 배열의 요소인 것처럼 메모리에 순차적으로 배치되어야 합니다.
인터페이스에 있는 field_declaration은 fixed_size_buffer_declarator와 함께 static
한정자가 있어야 합니다.
상황에 따라(세부 정보는 아래에 지정됨) 고정 크기 버퍼 멤버에 대한 액세스는 System.ReadOnlySpan<S>
또는 System.Span<S>
값(변수가 없음)으로 분류됩니다. 여기서 S는 고정 크기 버퍼의 요소 형식입니다. 두 형식 모두 적절한 "readonly-ness"를 사용하여 특정 요소에 대한 참조를 반환하는 인덱서를 제공하므로 언어 규칙이 이를 허용하지 않을 때 요소에 직접 할당할 수 없습니다.
이렇게 하면 고정 크기 버퍼 요소 형식으로 사용할 수 있는 형식 집합을 형식 인수로 사용할 수 있는 형식으로 제한합니다. 예를 들어 포인터 형식은 요소 형식으로 사용할 수 없습니다.
결과 범위 인스턴스의 길이는 고정 크기 버퍼에 선언된 크기와 같습니다. 선언된 고정 크기 버퍼 범위를 벗어난 상수 식을 사용하여 범위로 인덱싱하는 것은 컴파일 시간 오류입니다.
값의 안전 컨텍스트는 마치 백업 데이터가 필드로 액세스된 경우처럼 컨테이너의 안전 컨텍스트와 동일합니다.
식의 고정 크기 버퍼
고정 크기 버퍼 멤버의 멤버 조회는 필드의 멤버 조회와 똑같이 진행됩니다.
simple_name 또는 member_access 사용하여 식에서 고정 크기 버퍼를 참조할 수 있습니다.
인스턴스 고정 크기 버퍼 멤버를 단순 이름으로 참조하는 경우 효과는 this.I
양식의 멤버 액세스와 동일합니다. 여기서 I
고정 크기 버퍼 멤버입니다. 고정 고정 크기 버퍼 멤버를 단순 이름으로 참조하는 경우 효과는 E.I
양식의 멤버 액세스와 동일합니다. 여기서 I
고정 크기 버퍼 멤버이고 E
선언 형식입니다.
읽기 전용이 아닌 고정 크기 버퍼
폼 E.I
멤버 액세스에서 E
구조체 형식이고 해당 구조체 형식의 I
멤버 조회가 읽기 전용이 아닌 인스턴스 고정 크기 멤버를 식별하는 경우 E.I
평가되고 다음과 같이 분류됩니다.
-
E
값으로 분류되는 경우E.I
System.Index
형식의 인덱스가 있는 요소 액세스 또는 암시적으로 int로 변환할 수 있는 형식의 primary_no_array_creation_expression만 사용할 수 있습니다. 요소 액세스의 결과는 지정된 위치에 있는 고정 크기 멤버의 요소로, 값으로 분류됩니다. - 그렇지 않으면
E
읽기 전용 변수로 분류되고 식 결과가System.ReadOnlySpan<S>
형식의 값으로 분류되는 경우 S는I
요소 형식입니다. 이 값을 사용하여 멤버의 요소에 액세스할 수 있습니다. - 그렇지 않으면
E
쓰기 가능한 변수로 분류되고 식의 결과는System.Span<S>
형식의 값으로 분류됩니다. 여기서 S는I
요소 형식입니다. 이 값을 사용하여 멤버의 요소에 액세스할 수 있습니다.
멤버 액세스 형식이 E.I
인 경우, E
이 클래스 형식이고 해당 클래스 형식 내에서 I
멤버 조회가 비 읽기 전용 인스턴스 고정 크기 멤버를 식별하면, E.I
은 System.Span<S>
형식의 값으로 평가 및 분류됩니다. 여기서 S는 I
의 요소 형식입니다.
양식 E.I
의 멤버 액세스에서, I
의 멤버 조회가 읽기 전용이 아닌 고정 크기 정적 멤버를 식별하는 경우, E.I
는 System.Span<S>
형식의 값으로 평가되고 분류됩니다. 여기서 S는 I
의 요소 형식입니다.
읽기 전용 고정 크기 버퍼
field_declaration이 readonly
한정자를 포함하는 경우, fixed_size_buffer_declarator를 통해 도입된 멤버는 읽기 전용 고정 크기 버퍼입니다.
읽기 전용 고정 크기 버퍼의 요소에 대한 직접 할당은 동일한 형식의 인스턴스 생성자, init 멤버 또는 정적 생성자에서만 발생할 수 있습니다.
특히 읽기 전용 고정 크기 버퍼 요소에 대한 직접 할당은 다음 컨텍스트에서만 허용됩니다.
- 인스턴스 멤버의 경우, 멤버 선언을 포함하는 형식의 인스턴스 생성자 또는 초기화(init) 멤버에서 초기화됩니다. 정적 멤버의 경우, 멤버 선언을 포함하는 형식의 정적 생성자에서 초기화됩니다. 또한 읽기 전용 고정 크기 버퍼의 요소를
out
또는ref
매개 변수로 전달하는 것이 유효한 유일한 컨텍스트입니다.
읽기 전용 고정 크기 버퍼의 요소에 할당하거나 다른 컨텍스트에서 out
또는 ref
매개 변수로 전달하려고 하면 컴파일 시간 오류가 발생합니다.
이 작업은 다음을 통해 수행됩니다.
읽기 전용 고정 크기 버퍼에 대한 멤버 액세스는 다음과 같이 평가되고 분류됩니다.
- 폼
멤버 액세스에서, 만약 이 구조체 형식이고 가 값으로 분류된다면, 은 형식의 인덱스를 가진 요소 액세스로서 혹은 int로 암시적으로 변환 가능한 형식의 primary_no_array_creation_expression 로만 사용할 수 있습니다. 요소 액세스의 결과는 지정된 위치에서 고정 크기 멤버의 요소로, 값으로 분류됩니다. - 읽기 전용 고정 크기 버퍼 요소에 대한 직접 할당이 허용되는 컨텍스트에서 액세스가 발생하는 경우 식의 결과는
System.Span<S>
형식의 값으로 분류됩니다. 여기서 S는 고정 크기 버퍼의 요소 형식입니다. 이 값을 사용하여 멤버의 요소에 액세스할 수 있습니다. - 그렇지 않으면 식은
System.ReadOnlySpan<S>
형식의 값으로 분류됩니다. 여기서 S는 고정 크기 버퍼의 요소 형식입니다. 이 값을 사용하여 멤버의 요소에 액세스할 수 있습니다.
명확한 과제 확인
고정 크기 버퍼에는 명확한 할당 확인이 적용되지 않으며 구조체 형식 변수의 명확한 할당 검사를 위해 고정 크기 버퍼 멤버는 무시됩니다.
고정 크기 버퍼 멤버가 정적이거나 고정 크기 버퍼 멤버의 구조체 변수를 포함하는 가장 바깥쪽이 정적 변수, 클래스 인스턴스의 인스턴스 변수 또는 배열 요소인 경우 고정 크기 버퍼의 요소가 자동으로 기본값으로 초기화됩니다. 다른 모든 경우에서는 고정 크기 버퍼의 초기 콘텐츠가 정의되지 않습니다.
메타데이터
메타데이터 내보내기 및 코드 생성
메타데이터 인코딩 컴파일러는 최근 추가된 System.Runtime.CompilerServices.InlineArrayAttribute
에 의존할 것입니다.
다음과 같은 의사 코드로 된 고정 크기 버퍼:
// Not valid C#
public partial class C
{
public int buffer1[10];
public readonly int buffer2[10];
}
는 특별히 데코레이팅된 구조체 형식의 필드로 내보내질 것입니다.
해당하는 C# 코드는 다음과 같습니다.
public partial class C
{
public Buffer10<int> buffer1;
public readonly Buffer10<int> buffer2;
}
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
[UnscopedRef]
public System.Span<T> AsSpan()
{
return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref _element0, 10);
}
[UnscopedRef]
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
{
return System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan(
ref System.Runtime.CompilerServices.Unsafe.AsRef(in _element0), 10);
}
}
형식 및 해당 멤버에 대한 실제 명명 규칙은 TBD입니다. 프레임워크에는 제한된 버퍼 크기 집합을 포함하는 미리 정의된 "버퍼" 형식 집합이 포함될 수 있습니다. 미리 정의된 형식이 없으면 컴파일러가 빌드 중인 모듈에서 이를 합성합니다. 생성된 형식의 이름은 다른 언어에서 사용하기 쉽게 발음할 수 있도록 지정됩니다.
다음과 같은 액세스에 대해 생성된 코드입니다.
public partial class C
{
void M1(int val)
{
buffer1[1] = val;
}
int M2()
{
return buffer2[1];
}
}
에 해당합니다.
public partial class C
{
void M1(int val)
{
buffer.AsSpan()[1] = val;
}
int M2()
{
return buffer2.AsReadOnlySpan()[1];
}
}
메타데이터 가져오기
컴파일러가 T 형식의 필드 선언을 가져오면 다음 조건이 모두 충족됩니다.
-
T는
InlineArray
특성이 적용된 구조체 형식이며, - T 내에 선언된 첫 번째 인스턴스 필드에는 F형식이 있습니다.
-
public System.Span<F> AsSpan()
는 T내에 있습니다. -
T내에
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
또는public System.ReadOnlySpan<T> AsReadOnlySpan()
있습니다.
이 필드는 요소 형식이 F
메서드 또는 속성 그룹(예를 들어, 프로그래밍 언어의 접근 방식)
한 가지 생각은 이러한 멤버를 메서드 그룹처럼 취급하는 것입니다. 이를 통해, 이들이 자동으로 값으로 간주되지 않지만, 필요시 값으로 변환할 수 있도록 하는 것입니다. 작동 방법은 다음과 같습니다.
- 안전한 고정 크기 버퍼 액세스에는 고유한 분류(예: 메서드 그룹 및 람다)가 있습니다.
- 스팬 형식이 아닌 언어 작업으로 직접 인덱싱하여 변수를 생성할 수 있습니다(버퍼가 읽기 전용 컨텍스트인 경우 구조체의 필드와 동일).
- 식에서
Span<T>
및ReadOnlySpan<T>
로의 암시적 변환이 있지만, 읽기 전용 컨텍스트에 있는 경우Span<T>
을 사용하는 것은 오류입니다. - 이들의 자연 유형은
ReadOnlySpan<T>
이며, 형식 유추(var, best-common-type 또는 generic) 참여 시 해당 유형으로 기여합니다.
C/C++ 고정 크기 버퍼
C/C++에는 고정 크기 버퍼의 개념이 다릅니다. 예를 들어 데이터가 "가변 길이"임을 나타내는 방법으로 자주 사용되는 "길이가 0인 고정된 버퍼"라는 개념이 있습니다. 이 제안과 상호 운용할 수 있는 것은 이 제안의 목표가 아닙니다.
LDM 모임
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-03.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-10.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-17.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#inline-arrays-as-record-structs
C# feature specifications