Поделиться через


Встроенные массивы

Заметка

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Она включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.

Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия фиксируются в соответствующих заметках о собрании по проектированию языка (LDM) .

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Сводка

Предоставьте универсальный и безопасный механизм для использования типов структур, использующих функцию InlineArrayAttribute. Предоставьте универсальный и безопасный механизм для объявления встроенных массивов в классах C#, структурах и интерфейсах.

Примечание. Предыдущие версии этой спецификации использовали термины "ref-safe-to-escape" и "safe-to-escape", которые были представлены в спецификации функции безопасности диапазона. Комитет по стандарту ECMA изменил имена на "ref-safe-context" и "safe-context"соответственно. Значения безопасного контекста были уточнены, чтобы согласованно использовать «declaration-block», «function-member» и «caller-context». Спецлеты использовали разные формулировки для этих терминов, а также использовали "безопасный для возврата" в качестве синонима для "контекста вызывающего абонента". Эта спецификация была обновлена, чтобы использовать термины в стандарте 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:

  • Размер типа расширен, чтобы вместить 10 элементов (число поступает из атрибута InlineArray) типа object (тип определяется типом единственного поля экземпляра в структуре, _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 ']'
    ;

argument_list элемента element_access не должно содержать аргументов 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_access как значение типа dynamic. Приведенные ниже правила для определения смысла element_access затем применяются во время выполнения, используя тип времени выполнения вместо типа времени компиляции для выражений primary_no_array_creation_expression и argument_list, которые имеют тип времени компиляции dynamic. Если primary_no_array_creation_expression не имеет типа, определяемого во время компиляции dynamic, доступ к элементу подвергается ограниченной проверке на этапе компиляции, как описано в §11.6.5.

Если primary_no_array_creation_expression элемента element_access имеет значение типа array_type, то element_access представляет собой доступ к массиву (§12.8.12.2). Если primary_no_array_creation_expression в element_access является переменной или значением встроенного массива, и argument_list состоит из одного аргумента, то element_access представляет собой встроенный доступ к элементу массива. В противном случае primary_no_array_creation_expression должен быть переменной или значением типа класса, структуры или интерфейса, имеющего одного или нескольких членов индексатора, в этом случае element_access является доступом к индексатору (§12.8.12.3).

Доступ к элементу встроенного массива

Для доступа к встроенному элементу массива primary_no_array_creation_expressionelement_access должен быть переменной или значением встроенного типа массива. Кроме того, argument_list доступа к элементу встроенного массива не разрешено содержать именованные аргументы. argument_list должен содержать одно выражение, и это выражение должно быть

  • типа 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 в primary_no_array_creation_expression. Для анализа ref-safety контексты ref-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 на primary_no_array_creation_expression. Для целей анализа безопасности ссылок контекст безопасной ссылки/безопасного контекста доступа эквивалентен тому же для вызова метода с такой сигнатурой static ref readonly T GetItem(in 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 в primary_no_array_creation_expression. Для анализа безопасного контекста ссылок в контексте/ доступ эквивалентен доступу при вызове метода с сигнатурой 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, которое затем преобразуется в индексное значение на основе int, как описано в https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, предполагая, что длина коллекции известна во время компиляции и равна количеству элементов в встроенном типе массива основного выражения без создания массива. Затем доступ к элементу интерпретируется как описано в разделе Если тип выражения находится в разделе.

Когда выражение неявно преобразуется в System.Range

Если primary_no_array_creation_expression является записываемой переменной, результатом оценки доступа к элементу встроенного массива является значение, эквивалентное вызову public Span<T> Slice (int start, int length) для экземпляра System.Span<T>, возвращаемого методом System.Span<T> InlineArrayAsSpan в primary_no_array_creation_expression. Для анализа ref-safety контекст ref-safe-context/безопасного контекста доступа эквивалентен контексту вызова метода с сигнатурой static System.Span<T> GetSlice(ref InlineArrayType array).

Если primary_no_array_creation_expression является переменной только для чтения, результат вычисления доступа к элементу встроенного массива эквивалентен вызову метода public ReadOnlySpan<T> Slice (int start, int length) на экземпляре System.ReadOnlySpan<T>, который возвращается методом System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan в primary_no_array_creation_expression. Для целей анализа безопасности ссылок контекст ref-safe-context/и безопасный контекст доступа эквивалентны тем же, что и для вызова метода с подписью static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array).

Если primary_no_array_creation_expression является значением, сообщается об ошибке.

Аргументы для вызова метода Slice вычисляются из выражения индекса, преобразованного в System.Range, как описано в https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, предполагая, что длина коллекции известна на этапе компиляции и равна числу элементов во встроенном типе массива primary_no_array_creation_expression.

Компилятор может опустить вызов Slice, если он известен во время компиляции, что start равно 0, а length меньше или равно количеству элементов в встроенном типе массива. Компилятор также может сообщить об ошибке, если известно во время компиляции, что срез выходит из встроенных границ массива.

Например:

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
}

Для анализа безопасности ссылок безопасный контекст преобразования эквивалентен безопасному контексту для вызова метода с подписью 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:

  • Целевой тип — это структура, не являющаяся записью.
  • Целевой тип имеет только одно поле
  • Указанная длина > 0
  • У целевой структуры нет явного макета

Встроенные элементы массива в инициализаторе объектов

По умолчанию инициализация элементов не поддерживается с помощью initializer_target формы '[' argument_list ']' (см. 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 будет изменен, чтобы разрешить использование типа массива "inline" в качестве коллекции в операторе 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);
}

Мы будем поддерживать foreach над встроенными массивами, даже если изначально он ограничен в методах async из-за участия типов диапазонов при переводе.

Открытые вопросы дизайна

Альтернативы

Синтаксис типа встроенного массива

Грамматика на 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, а значение должно быть ненулевым целым числом.

Соответствующая часть раздела 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.

Тип элемента массива определяется как тип, который получается после удаления крайнего левого rank_specifier.

  • Тип массива формы T[ constant_expression ] является анонимным встроенным типом массива с длиной, обозначаемой constant_expression и типом элементов, не относящихся к массиву, T.
  • Тип массива формы T[ constant_expression ][R₁]...[Rₓ] является анонимным встроенным типом массива с длиной, обозначенной constant_expression и типом элемента T[R₁]...[Rₓ].
  • Тип массива формы T[R] (где R не является константным_выражением) — это обычный тип массива с рангом R и типом элемента T, который не является массивом.
  • Тип массива формы T[R][R₁]...[Rₓ] (где R не равно constant_expression) — это регулярный тип массива с ранжированием R и типом элемента T[R₁]...[Rₓ].

В действительности rank_specifierсчитываются слева направо , прежде чем конечный тип элемента, отличного от массива.

Пример: тип в int[][,,][,] представляет собой одномерный массив трёхмерных массивов двумерных массивов int. конечный пример

Во время выполнения значение обычного типа массива может быть null или ссылкой на экземпляр этого типа массива.

примечание. В соответствии с правилами https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covarianceзначение также может быть ссылкой на ковариантный тип массива. конечная сноска

Анонимный инлайн-тип массива — это синтезированный компилятором инлайн-тип массива с внутренней доступностью. Тип элемента должен быть типом, который можно использовать в качестве аргумента типа. В отличие от явно объявленного типа встроенного массива анонимный тип встроенного массива нельзя ссылаться по имени, он может ссылаться только по синтаксису array_type. В контексте той же программы любые два array_type, обозначающие типы встроенных массивов одного и того же типа элемента и одинаковой длины, обозначают один и тот же анонимный тип встроенного массива.

Помимо внутренней доступности компилятор предотвратит использование API с применением анонимных встроенных типов массивов через границы сборки, используя обязательный пользовательский модификатор (точный тип TBD), применяемый к анонимной ссылке на тип массива в сигнатуре.

Выражения создания массива

выражения создания массива

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 уже имеет значение выделения обычного одномерного типа массива указанной длины. Таким образом, array_creation_expression будет продолжать представлять выделение регулярного массива.

Однако новую форму rank_specifier можно использовать для включения анонимного типа встроенного массива в тип элемента выделенного массива.

Например, следующие выражения создают регулярный массив длиной 2 с типом элемента анонимного встроенного массива с элементами типа int и длиной 5.

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.

В этом дизайне типы буферов фиксированного размера не получают общего особого обращения в языке. Существует специальный синтаксис для объявления элементов, представляющих буферы фиксированного размера и новые правила для использования этих элементов. Они не являются полями с точки зрения языка.

Грамматика для variable_declarator в https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields будет расширена, чтобы разрешить указание размера буфера:

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 представлен буфер фиксированного размера заданного типа элемента.

Тип элемента буфера — это типа , указанного в . Декларатор буфера фиксированного размера вводит новый член и состоит из идентификатора, который именует член, с последующим константным выражением, заключённым в маркеры [ и ]. Константное выражение обозначает количество элементов в элементе, введенном этим декларатором буфера фиксированного размера. Тип константного выражения должен быть неявно преобразован в тип int, а значение должно быть ненулевым положительным целым числом.

Элементы буфера фиксированного размера должны быть последовательно размещены в памяти, как будто они являются элементами массива.

field_declaration с fixed_size_buffer_declarator в интерфейсе обязан иметь модификатор static.

В зависимости от ситуации (сведения указаны ниже), доступ к члену буфера фиксированного размера классифицируется как значение (никогда не переменная) либо System.ReadOnlySpan<S> или System.Span<S>, где S является типом элемента буфера фиксированного размера. Оба типа предоставляют индексаторы, возвращающие ссылку на определенный элемент с соответствующей "неизменяемостью", что предотвращает прямое присваивание элементам, если правила языка этого не позволяют.

Это ограничивает набор типов, которые можно использовать в качестве типа буферного элемента фиксированного размера, к типам, которые можно использовать в качестве аргументов типа. Например, тип указателя нельзя использовать в качестве типа элемента.

Результирующий экземпляр диапазона будет иметь длину, равную значению размера, заданному в буфере фиксированного размера. Индексирование диапазона, используя константное выражение, за границами фиксированного буфера, приводит к ошибке на стадии компиляции.

безопасного контекста значения будет равен безопасного контекста контейнера, так же как если бы к резервным данным обращались как к полю.

Буферы фиксированного размера в выражениях

Поиск элемента буфера фиксированного размера выполняется точно так же, как поиск элемента поля.

Буфер фиксированного размера может быть использован в выражении через simple_name или member_access.

Если элемент буфера фиксированного размера экземпляра ссылается на простое имя, эффект совпадает с доступом к элементу формы this.I, где I является членом буфера фиксированного размера. Если статический элемент буфера фиксированного размера ссылается как простое имя, эффект совпадает с доступом к члену формы E.I, где I является членом буфера фиксированного размера и E является декларируемым типом.

Буферы фиксированного размера, не только для чтения

В доступе к члену формы E.I, если E имеет тип структуры, и поиск члена I в этом типе структуры идентифицирует нестатический член экземпляра с фиксированным размером, не являющийся доступным только для чтения, то E.I вычисляется и классифицируется следующим образом:

  • Если E классифицируется как значение, E.I можно использовать только в качестве primary_no_array_creation_expression доступа к элементу с индексом типа System.Index или типа, неявно преобразованного в 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_declaration включает модификатор readonly, элемент, введенный fixed_size_buffer_declarator, является буфером фиксированного размера. Прямые присваивания элементам буфера фиксированного размера, который только для чтения, могут выполняться только в конструкторе экземпляра, элементе init или статическом конструкторе в том же классе. В частности, прямые назначения элементу буфера только для чтения фиксированного размера допускаются только в следующих контекстах:

  • Для элемента экземпляра в конструкторах экземпляров или в элементе инициализации типа, содержащего объявление члена; для статического элемента в статическом конструкторе типа, содержащего объявление члена. Это также единственные контексты, в которых допустимо передать элемент буфера фиксированного размера только для чтения в качестве параметра out или ref.

Попытка назначить элемент буфера фиксированного размера для чтения или передать его в качестве параметра out или ref в любом другом контексте является ошибкой во время компиляции. Это достигается следующим образом.

Доступ к элементу буфера c фиксированным размером, который является только для чтения, оценивается и классифицируется следующим образом:

  • В доступе к элементу формы , если имеет тип структуры и классифицируется как значение, можно использовать только в качестве 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);
    }
}

Фактические соглашения об именовании для типа и его членов будут определены позднее. Платформа, скорее всего, будет включать набор предопределенных типов буферов, охватывающих ограниченный набор размеров буфера. Если предопределенный тип не существует, компилятор будет синтезировать его в созданном модуле. Имена созданных типов будут легко произносимыми для использования на других языках.

Код, созданный для доступа, например:

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().

Поле будет рассматриваться как буфер фиксированного размера C# с типом элемента F. В противном случае поле будет рассматриваться как обычное поле типа T.

Метод или группа свойств, подобная подходу в языке.

Эти члены следует рассматривать больше как группы методов, поскольку они сами по себе не являются значениями автоматически, но при необходимости могут ими стать. Вот как это будет работать:

  • Безопасный доступ к буферу фиксированного размера имеет собственную классификацию (например, группы методов и лямбда-файлы)
  • Их можно индексировать непосредственно в качестве языковой операции (а не через типы диапазонов) для создания переменной (которая является только для чтения, если буфер находится в контексте только для чтения, точно так же, как поля структуры).
  • Они имеют неявные преобразования выражений в Span<T> и ReadOnlySpan<T>, но использование первого является ошибкой, если они находятся в контексте только для чтения.
  • Их естественный тип ReadOnlySpan<T>, поэтому именно его они вносят, если участвуют в выводе типов (например, var, наилучший общий тип или обобщённый тип).

Буферы фиксированного размера C/C++

C/C++ имеет другое понятие буферов фиксированного размера. Например, существует понятие "буферы фиксированного размера нулевой длины", которое часто используется в качестве способа указать, что данные являются переменной длиной. Цель этого предложения не в том, чтобы взаимодействовать с этим.

Собрания LDM