次の方法で共有


インライン配列

手記

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

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

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

概要

構造体型を取り扱うために、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 機能を利用した構造体型の要素へのアクセス
  • structclass、または 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 も、標準では型と境界のチェックを提供しません。

言語は、インライン配列型の要素にアクセスするためのタイプセーフ/refセーフな方法を提供します。 アクセスはスパン ベースになります。 これにより、型引数として使用できる要素型を持つインライン配列型のサポートが制限されます。 たとえば、ポインター型を要素型として使用することはできません。 その他の例としてスパン型があります。

インライン配列型のスパン型のインスタンスの取得

インライン配列型の最初の要素が型の先頭 (ギャップなし) に配置されていることが保証されているため、コンパイラは次のコードを使用して 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_accessargument_list には、ref または out 引数を含めることはできません。

次の少なくとも 1 つが保持されている場合、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_accessdynamic型の値として分類します。 以下の規則は、element_access の意味を判断するために、コンパイル時型 dynamic がある primary_no_array_creation_expression 式および argument_list 式のコンパイル時の型ではなく、実行時の型を使用して実行時に適用されます。 primary_no_array_creation_expression にコンパイル時の型 dynamic がない場合、§12.6.5 で説明されているように、要素アクセスは制限されたコンパイル時チェックを実行します。

element_accessprimary_no_array_creation_expressionarray_type の値である場合、element_access は、配列アクセス (§12.8.12.2) です。 element_accessprimary_no_array_creation_expression がインライン配列型の変数または値で、その argument_list が単一の引数で構成されている場合、element_access はインライン配列の要素アクセスとなります。 それ以外の場合、primary_no_array_creation_expression は、1 つ以上のインデクサー メンバーを持つクラス、構造体、またはインターフェイス型の変数または値になります。その場合、element_access はインデクサー アクセスです (§12.8.12.3)。

インライン配列要素のアクセス

インライン配列要素アクセスの場合、element_accessprimary_no_array_creation_expression は、インライン配列型の変数または値である必要があります。 さらに、インライン配列要素アクセスの argument_list には、名前付き引数を含めることはできません。 argument_list には 1 つの式が含まれている必要があり、式は

  • 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/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 分析の目的では、アクセスの 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_expressionSystem.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_expressionSystem.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_expressionSystem.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で説明されているように、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 アプリケーションの次の側面を検証します。

  • ターゲット型はレコード以外の構造体
  • ターゲット型にフィールドが 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 は、配列がランク 1 に rank_specifierの "," トークンの数を加算した配列であることを示します。

配列型の要素型は、左端の 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 は constant_expressionではありません) は、ランクが 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は、同じ匿名インライン配列型を参照します。

コンパイラは、内部アクセシビリティに加えて、シグネチャの匿名インライン配列型参照に適用される必要なカスタム修飾子 (正確な型 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
    ;

現在の文法では、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-fieldsvariable_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 修飾子が必要です。

状況に応じて (詳細は以下で指定します)、固定サイズバッファー メンバーへのアクセスは、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 タイプのインデックスを持つ要素アクセスprimary_no_array_creation_expression としてのみ使用できるか、暗黙的に int に変換可能なタイプとして使用できます。要素アクセスの結果は、指定された位置にある固定サイズのメンバーの要素であり、値として分類されます。
  • それ以外の場合、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_declarationreadonly 修飾子が含まれる場合、fixed_size_buffer_declarator によって導入されるメンバーは読み取り専用固定サイズ バッファーです。 読み取り専用の固定サイズ バッファーの要素への直接割り当ては、同じ型のインスタンス コンストラクター、init メンバー、または静的コンストラクターでのみ行うことができます。 具体的には、読み取り専用の固定サイズ バッファーの要素への直接割り当ては、次のコンテキストでのみ許可されます。

  • インスタンス メンバーの場合は、メンバー宣言を含む型のインスタンス コンストラクターや init メンバーの中で行います。静的メンバーの場合は、メンバー宣言を含む型の静的コンストラクターの中で行います。 これらは、読み取り専用の固定サイズ バッファーの要素を out または ref パラメーターとして渡すことが有効な唯一のコンテキストでもあります。

読み取り専用の固定サイズ バッファーの要素に割り当てたり、他のコンテキストで out または ref パラメーターとして渡そうとすると、コンパイル時エラーになります。 これは、次の方法で実現されます。

読み取り専用の固定サイズ バッファーのメンバー アクセスが評価され、次のように分類されます。

  • フォーム E.Iのメンバー アクセスでは、E が構造体型で、E が値として分類されている場合、E.I は、System.Index 型のインデックスを持つ 要素アクセスprimary_no_array_creation_expression として、または暗黙的に int に変換できる型としてのみ使用できます。要素アクセスの結果は、指定した位置にある固定サイズのメンバーの要素であり、値として分類されます。
  • 読み取り専用の固定サイズ バッファーの要素への直接割り当てが許可されているコンテキストでアクセスが発生した場合、式の結果は 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型があります。
  • 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>がありますが、読み取り専用コンテキストにある場合、前者の使用はエラーになります
  • その自然型は ReadOnlySpan<T>であるため、型推論に参加する場合に貢献します (var、best-common-type、generic など)

C/C++ 固定サイズ バッファー

C/C++ には、固定サイズ バッファーの概念が異なります。 たとえば、"長さ 0 の固定サイズのバッファー" という概念があり、データが "可変長" であることを示す方法としてよく使用されます。 この提案と相互運用できることは、この提案の目的ではありません。

LDM会議