インライン配列
メモ
この記事は機能仕様についてです。 仕様は、機能の設計ドキュメントとして使用できます。 これには、提案された仕様の変更および機能の設計と開発時に必要な情報が含まれます。 これらの記事は、提案された仕様の変更が決定され、現在の ECMA 仕様に組み込まれるまで公開されます。
機能の仕様と行われた実装では、いくつかの違いがあることがあります。 これらの違いは、関連する言語設計ミーティング (LDM) メモに取り上げられています。
機能仕様を C# 言語標準に導入するプロセスの詳細については、仕様に関する記事を参照してください。
チャンピオンの課題: https://github.com/dotnet/csharplang/issues/7431
まとめ
構造体型を取り扱うために、InlineArrayAttribute 機能を利用して汎用かつ安全なメカニズムを提供します。 C# クラス、構造体、およびインターフェイス内でインライン配列を宣言するための汎用で安全なメカニズムを提供します。
注: この仕様の以前のバージョンでは、Span safety 機能仕様で導入された用語「ref-safe-to-escape」および「safe-to-escape」が使用されていました。 ECMA 標準委員会 は、名前をそれぞれ "ref-safe-context" と "safe-context"に変更しました。 セーフ コンテキストの値は、「declaration-block」、「function-member」、「caller-context」を一貫して使用するように調整されています。 スペックレットではこれらの用語に対して異なる表現を使用しており、「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
のマネージド型とアンマネージド型のインライン配列の宣言。
また、言語安全性の検証を行います。
詳細な設計
最近、ランタイムに インライン配列属性 機能が追加されました。 つまり、ユーザーは次のような構造体型を宣言できます。
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
ランタイムは、Buffer
型に対して特殊な型レイアウトを指定します。
- 型のサイズは、
object
型の 10 個の要素 (InlineArray 属性から取得) に合うように拡張されます (型は構造体内の唯一のインスタンス フィールドの型から取得されます。この例では_element0
)。 - 最初の要素は、インスタンス フィールドと構造体の先頭に配置されます。
- 要素は、配列の要素であるかのように、メモリ内で順番にレイアウトされます。
ランタイムは、構造体内のすべての要素に対して通常の GC 追跡を指定します。
この提案では、このような型を "インライン配列型" と見なします。
インライン配列型の要素には、ポインターまたは System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan API が返すスパン インスタンス経由でアクセスできます。 ただし、ポインターアプローチも API も、標準では型と境界のチェックを提供しません。
言語は、インライン配列型の要素にアクセスするための type-safe/ref-safe な方法を指定します。 アクセスはスパン ベースになります。 これにより、型引数として使用できる要素型を持つインライン配列型のサポートが制限されます。 たとえば、ポインター型を要素型として使用することはできません。 その他の例としてスパン型があります。
インライン配列型のスパン型のインスタンスを取得する
インライン配列型の最初の要素が型の先頭 (ギャップなし) に配置されることが保証されているため、コンパイラは次のコードを使用して 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 サイズを小さくするには、コンパイラは、プライベート実装の詳細型に 2 つの汎用再利用可能ヘルパーを追加し、同じプログラム内のすべての使用サイトで使用できるようにする必要があります。
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 は、1 つ以上の 引数コンマで区切って構成されます。
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 の少なくとも 1 つの式がコンパイル時の型
dynamic
を持ち、primary_no_array_creation_expression が配列型を持たず、かつ primary_no_array_creation_expression がインライン配列型を持たない、または引数リストに複数の項目がある。
この場合、コンパイラは element_access を dynamic
型の値として分類します。 以下の規則は、element_access の意味を判断するために、コンパイル時型 がある primary_no_array_creation_expression 式および dynamic
式のコンパイル時の型ではなく、実行時の型を使用して実行時に適用されます。 primary_no_array_creation_expression にコンパイル時の型 dynamic
がない場合、§12.6.5 で説明されているように、要素アクセスは制限されたコンパイル時チェックを実行します。
element_access の primary_no_array_creation_expression が array_type の値である場合、element_access は、配列アクセス (§12.8.12.2) です。 element_access の primary_no_array_creation_expression がインライン配列型の変数または値で、その argument_list が単一の引数で構成されている場合、element_access はインライン配列の要素アクセスとなります。 それ以外の場合、 primary_no_array_creation_expression は、1 つ以上のインデクサー メンバーを持つクラス、構造体、またはインターフェイス型の変数または値になります。その場合、 element_access はインデクサー アクセスです (§12.8.12.3)。
インライン配列要素のアクセス
インライン配列要素アクセスの場合、element_access の primary_no_array_creation_expression は、インライン配列型の変数または値である必要があります。 さらに、インライン配列要素アクセスの argument_list に名前付き引数を含めることはできません。 argument_list には 1 つの式が含まれている必要があり、式は
- 型
int
、または - 暗黙的に
int
に変換できます。または - 暗黙的に
System.Index
に変換できます。または - 暗黙的に
System.Range
に変換できます。
式の型が int の場合
primary_no_array_creation_expression が書き込み可能な変数である場合、インライン配列要素へのアクセスを評価した結果は、public ref T this[int index] { get; }
のメソッド System.Span<T>
によって返される System.Span<T> InlineArrayAsSpan
のインスタンスに対して、その整数値で を呼び出すことと同等の書き込み可能な変数になります。 ref-safety 分析の目的では、アクセスの ref-safe-context/safe-context は、署名 static ref T GetItem(ref InlineArrayType array)
を持つメソッドの呼び出しの場合と同じです。
結果の変数は、primary_no_array_creation_expression が移動可能な場合にのみ移動可能と見なされます。
primary_no_array_creation_expression が読み取り専用変数の場合、インライン配列要素アクセスを評価した結果として得られる読み取り専用変数は、public ref readonly T this[int index] { get; }
のメソッド System.ReadOnlySpan<T>
によって返された System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
のインスタンス上で整数値を用いて を呼び出した場合に相当します。 ref-safety 分析の目的では、アクセスの ref-safe-context/safe-context は、署名 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/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
に変換され、コンパイル時にコレクションの長さが既知であり、primary_no_array_creation_expression のインライン配列型の量が同等であると仮定し、https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support で説明されている通り int ベースのインデックス値に変換されます。 次に、要素アクセスは、「式の型が 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/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/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 で説明されているように、コンパイル時に、コレクションの長さが既知であり、primary_no_array_creation_expression のインライン配列型の要素の量が同等であると仮定して、System.Range
に変換されたインデックス式から計算されます。
コンパイル時に、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
コレクション リテラルの長さは、対象のインライン配列型の長さと一致する必要があります。 コンパイル時にリテラルの長さがわかっていて、ターゲットの長さと一致しない場合は、エラーが報告されます。 不一致が発生すると、実行時に例外が発生します。 正確な例外の種類は未定です。 候補には、System.NotSupportedException、System.InvalidOperationException があります。
InlineArrayAttribute アプリケーションの検証
コンパイラは、InlineArrayAttribute アプリケーションの次の側面を検証します。
- ターゲット型はレコード以外の構造体
- ターゲット型にフィールドが 1 つだけ含まれる
- 指定された長さ > 0
- ターゲット構造体に明示的なレイアウトが指定されていない
オブジェクト初期化子のインライン配列要素
既定では、要素の初期化は形式 '[' 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 ステートメントのコレクションとしてインライン配列型を使用できるように調整されます。
次に例を示します。
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 で指定されています。
配列タイプは、1 つ以上の rank_specifier が続く non_array_type として記述されます。
non_array_type は、それ自体が array_type ではない任意の型です。
配列型のランクは、array_type の左端にある rank_specifier が指定します。rank_specifier は、配列が rank_specifier の「,
」トークンの数に 1 を足したランクの配列であることを示します。
配列型の要素は、左端の rank_specifier を削除した結果の型です。
- 形式
T[ constant_expression ]
の配列型は、constant_expression で示される長さの無名のインライン配列型であり、非配列要素型T
です。 - 形式
T[ constant_expression ][R₁]...[Rₓ]
の配列型は、constant_expression で示される長さの無名のインライン配列型であり、要素型T[R₁]...[Rₓ]
です。 - (R が constant_expression ではない) 形式
T[R]
の配列型は、ランクR
の通常の配列型と非配列要素型T
です。 - (R が constant_expression ではない) 形式
T[R][R₁]...[Rₓ]
の配列型は、ランクR
の通常の配列型と要素型T[R₁]...[Rₓ]
です。
実際には、rank_specifier は、最後の非配列要素タイプの前に左から右に読み取られます。
例:
int[][,,][,]
の型は、int
の 2 次元配列からなる 3 次元配列の 1 次元配列です。 終了サンプル
実行時には、通常の配列型の値を null
にしたり、その配列型のインスタンスへの参照を指定したりできます。
注意: https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariance のルールに従って、値は共変配列型への参照になる場合があります。 注釈
匿名インライン配列型は、内部アクセシビリティを持つコンパイラによって合成されたインライン配列型です。 要素型は、型引数として使用できる型である必要があります。 明示的に宣言されたインライン配列型とは異なり、匿名インライン配列型は名前で参照できません。array_type 構文でのみ参照できます。 同じプログラムのコンテキストでは、同じ要素型と同じ長さのインライン配列型を示す 2 つの array_type は、同じ匿名インライン配列型を参照します。
内部アクセシビリティに加えて、コンパイラは、シグネチャの匿名インライン配列型参照に適用される必要なカスタム修飾子 (正確な型は未定) を使用して、アセンブリ境界を越えて匿名インライン配列型を利用する API の使用を防ぎます。
配列作成式
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
現在の文法では、expression_list の代わりに constant_expression を使用すると、指定した長さの通常の 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 以外の整数にする必要があります。
固定サイズ バッファーの要素は、配列の要素であるかのようにメモリ内に順番に配置する必要があります。
インターフェース内の fixed_size_buffer_declarator を持つ field_declaration には static
修飾子が必要です。
状況に応じて (詳細は以下で指定します)、固定サイズ バッファー メンバーへのアクセスは、S が固定サイズ バッファーの要素型である System.ReadOnlySpan<S>
または System.Span<S>
のいずれかの値 (変数ではありません) として分類されます。 どちらの型も、適切な "readonly-ness" を持つ特定の要素への参照を返すインデクサーを提供します。これにより、言語ルールで許可されていない場合に要素に直接割り当てられません。
これにより、固定サイズ バッファー要素型として使用できる型一式が、型引数として使用できる型に制限されます。 たとえば、ポインター型を要素型として使用することはできません。
結果のスパン インスタンスの長さは、固定サイズ バッファーで宣言されたサイズと同じになります。 宣言された固定サイズ バッファーの境界外の定数式を使用してスパンをインデックス化すると、コンパイル時エラーになります。
値の safe-context は、バッキング データがフィールドとしてアクセスされた場合と同様に、コンテナーの safe-context と同じになります。
式における固定サイズのバッファー
固定サイズ バッファー メンバーのメンバー検索は、フィールドのメンバー検索とまったく同じように処理されます。
固定サイズ バッファーは、simple_name または member_access を使用して式で参照できます。
インスタンス固定サイズ バッファー メンバーが単純な名前として参照されている場合、効果は、I
が固定サイズ バッファー メンバーである フォーム this.I
のメンバー アクセスと同じになります。 静的固定サイズ バッファー メンバーが単純な名前として参照されている場合、効果は、I
が固定サイズ バッファー メンバーである フォーム E.I
のメンバー アクセスと同じで、E
は宣言型になります。
読み取り専用ではない固定サイズ バッファー
フォーム E.I
のメンバー アクセスで、E
が構造体型で、その構造体型の I
のメンバー検索によって読み取り専用でないインスタンスの固定サイズ メンバーが識別された場合、E.I
が評価され、次のように分類されます。
E
が値として分類されている場合、E.I
は、 タイプのインデックスを持つ要素アクセス のSystem.Index
としてのみ使用できるか、暗黙的に int に変換可能なタイプとして使用できます。要素アクセスの結果は、指定された位置にある固定サイズのメンバーの要素であり、値として分類されます。- それ以外の場合、
E
が読み取り専用変数として分類される場合、式の結果が、S がI
の要素型であるSystem.ReadOnlySpan<S>
型の値として分類されます。 この値は、メンバーの要素にアクセスするために使用できます。 - それ以外の場合、
E
が書き込み専用変数として分類され、式の結果が、S がI
の要素型であるSystem.Span<S>
型の値として分類されます。 この値は、メンバーの要素にアクセスするために使用できます。
フォーム E.I
のメンバー アクセスで、E
がクラス型であり、そのクラス型の I
のメンバー検索が非読み取り専用インスタンス固定サイズ メンバーとして分類される場合、E.I
が評価され、S がI
の要素型である System.Span<S>
の値として分類されます。
フォーム E.I
のメンバー アクセスで、I
のメンバー検索が非読み取り専用静的固定サイズ メンバーとして分類された場合、E.I
が評価され、S が I
の要素型である System.Span<S>
型の値として分類されます。
読み取り専用固定サイズ バッファー
field_declaration に readonly
修飾子が含まれる場合、fixed_size_buffer_declarator によって導入されるメンバーは読み取り専用固定サイズ バッファーです。
読み取り専用固定サイズ バッファーの要素への直接割り当ては、同じ型のインスタンス コンストラクター、init メンバー、または静的コンストラクターでのみ行うことができます。
具体的には、読み取り専用固定サイズ バッファーの要素への直接割り当ては、次のコンテキストでのみ許可されます。
- インスタンス メンバーの場合は、メンバー宣言を含む型のインスタンス コンストラクターや init メンバーの中で行います。静的メンバーの場合は、メンバー宣言を含む型の静的コンストラクターの中で行います。 これらは、読み取り専用固定サイズ バッファーの要素を
out
またはref
パラメーターとして渡すことができる唯一のコンテキストでもあります。
読み取り専用固定サイズ バッファーの要素に割り当てたり、他のコンテキストで out
または ref
パラメーターとして渡そうとすると、コンパイル時エラーになります。
これは、次の方法で実現されます。
読み取り専用固定サイズ バッファーのメンバー アクセスが評価され、次のように分類されます。
- フォーム
E.I
のアクセス メンバーで、E
が構造体型でE
が値として分類されている場合、System.Index
型のインデックスがある要素アクセスの primary_no_array_creation_expression または int に暗黙的に変換可能な型としてのみE.I
を使用できます。要素アクセスの結果は、指定位置にある固定サイズ メンバーの要素で、値として分類されます。 - 読み取り専用固定サイズ バッファーの要素への直接割り当てが許可されているコンテキストでアクセスが発生した場合、式の結果は、S が固定サイズ バッファーの要素型である 型
System.Span<S>
の値として分類されます。 この値は、メンバーの要素にアクセスするために使用できます。 - それ以外の場合、式は S が固定サイズ バッファーの要素型である型
System.ReadOnlySpan<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);
}
}
型とそのメンバーの実際の命名規則は未定です。 フレームワークには、限られたバッファー サイズ一式をカバーする定義済みの "バッファー" 型一式が含まれる場合があります。 事前定義済みの型が存在しない場合、コンパイラはビルド中のモジュールで型を合成します。 生成された型の名前は、他の言語からの使用をサポートするために "読み上げ可能" になります。
次のようなアクセス用に生成されたコード。
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];
}
}
Metadata import
コンパイラが、型 T のフィールド宣言をインポートすると、次の条件がすべて満たされます。
- T は、
InlineArray
属性で修飾された構造体型です。 - T ないで宣言された最初のインスタンス フィールドには、型 F があります。
- T 内に
public System.Span<F> AsSpan()
があります。 - T 内に
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
またはpublic System.ReadOnlySpan<T> AsReadOnlySpan()
があります。
フィールドは、F 要素型がある C# 固定サイズ バッファーとして扱われます。それ以外の場合は、フィールドは、型 T の標準フィールドとして扱われます。
メソッドまたはプロパティ グループ (言語でのアプローチなど)
1 つの考え方として、これらのメンバーをメソッド グループのように扱うことが挙げられます。つまり、メンバーは自動的にそれ自体の値ではなく、必要に応じて 1 つにすることができます。 この場合の仕組みは次のようになります。
- 安全な固定サイズ バッファー アクセスには、独自の分類があります (メソッド グループやラムダなど)。
- これらは、(スパン型ではなく) 言語操作として直接インデックス化され、変数が生成されます (バッファーが読み取り専用コンテキストにある場合は読み取り専用で、構造体のフィールドと同じです)。
- これには、
Span<T>
やReadOnlySpan<T>
への暗黙的な conversions-from-expression がありますが、読み取り専用コンテキストにある場合は、前者はエラーになります。 - その自然型は
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