Sdílet prostřednictvím


Inlinovaná pole

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách schůzce návrhu jazyka (LDM).

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/7431

Shrnutí

Poskytnout obecný a bezpečný mechanismus pro využívání typů struktur pomocí funkce InlineArrayAttribute. Poskytuje obecný a bezpečný mechanismus pro deklarování vložených polí v rámci tříd, struktur a rozhraní jazyka C#.

Poznámka: Předchozí verze této specifikace používaly termíny "ref-safe-to-escape" a "safe-to-escape", které byly zavedeny ve specifikaci funkce Span safety. Standardní výbor ECMA změnil názvy na "ref-safe-context" a "bezpečný kontext". Hodnoty bezpečného kontextu byly upřesněny tak, aby používaly "blok-deklarace", "funkce-člen" a "kontext-volajícího" konzistentně. Specifikace pro tyto termíny používaly různé formulace a také používaly "safe-to-return" jako synonymum pro "caller-context". Tato specifikace byla aktualizována tak, aby používala termíny ve standardu C# 7.3.

Motivace

Tento návrh si klade za cíl řešit mnoho omezení https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. Konkrétně má za cíl umožnit:

  • přístup k prvkům struktur pomocí funkce InlineArrayAttribute;
  • deklarace in-line polí pro spravované a nespravované typy v struct, classnebo interface.

A zajistěte pro ně ověření bezpečnosti jazyka.

Podrobný návrh

Modul runtime nedávno přidal funkci InlineArrayAttribute. Stručně řečeno, uživatel může deklarovat typ struktury jako následující:

[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
    private object _element0;
}

Modul runtime poskytuje zvláštní rozložení typu pro typ Buffer:

  • Velikost typu je rozšířena tak, aby odpovídala 10 (číslo pochází z atributu InlineArray) elementům typu „object“ (typ pochází z typu jediného pole instance ve struktuře, „_element0“ v tomto příkladu).
  • První prvek je zarovnán s polem instance a se začátkem struktury.
  • Prvky jsou rozloženy postupně v paměti, jako by se jedná o prvky pole.

Modul runtime poskytuje pravidelné sledování GC pro všechny prvky ve struktuře.

Tento návrh bude odkazovat na podobné typy jako "vložené typy polí".

K prvkům vloženého typu pole lze přistupovat prostřednictvím ukazatelů nebo prostřednictvím instance rozpětí vrácené rozhraními API System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan. Přístup ukazatele ani rozhraní API však neposkytují kontrolu typu a hranic.

Jazyk poskytne typově a referenčně bezpečný způsob přístupu k prvkům vložených typů polí. Přístup bude založený na časovém rozsahu. Toto omezení podporuje vložené typy polí s typy prvků, které lze použít jako argument typu. Například typ ukazatele nelze použít jako typ prvku. Další příklady typů rozsahu

Získání instancí typů úseků pro vnořený typ pole

Vzhledem k tomu, že existuje záruka, že první prvek typu vloženého pole je zarovnaný na začátku typu (bez mezery), kompilátor použije následující kód k dosažení hodnoty Span.

MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);

A následující kód pro získání hodnoty ReadOnlySpan:

MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);

Aby se zmenšila velikost IL na místech použití, měl by být kompilátor schopen přidat dva obecné, opakovaně použitelné pomocníky do typu pro soukromé detaily implementace a používat je napříč všemi místy použití v rámci stejného programu.

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);
}

Přístup k elementům

Přístup elementu k bude rozšířen tak, aby podporoval přístup k prvkům pole v řádku.

element_access se skládá z primary_no_array_creation_expression, po němž následuje token "[", poté argument_list, a nakonec token "]". argument_list se skládá z jednoho nebo více argumentů , které jsou odděleny čárkami.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

argument_listelement_access nesmí obsahovat ref ani out argumenty.

element_access je dynamicky svázaný (§11.3.3) v případě, že platí alespoň jeden z následujících:

  • primary_no_array_creation_expression má typ určený při kompilaci dynamic.
  • Nejméně jeden výraz argument_list má typ kompilace dynamic a primary_no_array_creation_expression nemá typ pole, a primary_no_array_creation_expression neobsahuje vložený typ pole nebo existuje více než jedna položka v seznamu argumentů.

V tomto případě kompilátor klasifikuje element_access jako hodnotu typu dynamic. Pravidla pro určení významu element_access se pak použijí za běhu programu, přičemž se používá typ za běhu místo typu doby kompilace u výrazů primary_no_array_creation_expression a argument_list, které mají typ kompilace dynamic. Pokud primary_no_array_creation_expression nemá typ kompilace dynamic, projde přístup k prvku omezenou kontrolou doby kompilace, jak je popsáno v §11.6.5.

Je-li primary_no_array_creation_expressionelement_access hodnota array_type, element_access je maticový přístup (§12.8.12.2). Pokud primary_no_array_creation_expression prvku element_access je proměnná nebo hodnota typu vloženého pole a argument_list se skládá z jediného argumentu, pak je element_access přístup k prvku vloženého pole. V opačném případě musí být primary_no_array_creation_expression proměnnou nebo hodnotou třídy, struktury nebo typu rozhraní, který má jeden nebo více členů indexeru, v takovém případě je element_access přístup indexeru (§12.8.12.3).

Přístup k prvku pole přímo v kódu

Pro přístup k prvkům vloženého pole musí být primary_no_array_creation_expressionelement_access proměnnou nebo hodnotou vloženého typu pole. Kromě toho argument_list při přístupu k prvku vloženého pole nesmí obsahovat pojmenované argumenty. argument_list musí obsahovat jeden výraz a výraz musí být

  • typu intnebo
  • implicitně konvertibilní na intnebo
  • implicitně konvertibilní na System.Indexnebo
  • implicitně konvertibilní na System.Range.
Když je typ výrazu int

Pokud je primary_no_array_creation_expression zapisovatelná proměnná, výsledek vyhodnocení přístupu k prvku pole je zapisovatelná proměnná, která je ekvivalentní volání public ref T this[int index] { get; } s danou celočíselnou hodnotou na instanci System.Span<T>, kterou vrací metoda System.Span<T> InlineArrayAsSpan na primary_no_array_creation_expression. Za účelem analýzy ref-safety jsou ref-safe-context/a safe-context přístupu ekvivalentní stejnému pro vyvolání metody s podpisem static ref T GetItem(ref InlineArrayType array). Výsledná proměnná se považuje za pohyblivou, pouze pokud primary_no_array_creation_expression je pohyblivá.

Pokud primary_no_array_creation_expression je proměnná jen pro čtení, výsledek vyhodnocení přístupu k vloženému prvku pole je proměnná jen pro čtení, což je ekvivalentní volání public ref readonly T this[int index] { get; } s danou celočíselnou hodnotou na instanci System.ReadOnlySpan<T> vrácenou metodou System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan na primary_no_array_creation_expression. Pro účely analýzy bezpečnosti referencí jsou /a bezpečný kontext přístupu ekvivalentní k tomu, co platí pro vyvolání metody s podpisem static ref readonly T GetItem(in InlineArrayType array). Výsledná proměnná se považuje za pohyblivou, pouze pokud primary_no_array_creation_expression je pohyblivá.

Pokud je primary_no_array_creation_expression hodnotou, výsledkem vyhodnocení přímého přístupu k prvku pole je hodnota, která je ekvivalentní vyvolání public ref readonly T this[int index] { get; } s danou celočíselnou hodnotou na instanci System.ReadOnlySpan<T>, kterou vrátí metoda System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan v rámci primary_no_array_creation_expression. Pro účely analýzy bezpečnosti referencí jsou bezpečné kontexty/a přístupu ekvivalentní k těm, které se používají pro vyvolání metody s podpisem static T GetItem(InlineArrayType array).

Například:

[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
}

Indexování do vloženého pole s konstantním výrazem mimo deklarované hranice je chyba při kompilaci.

Pokud je výraz implicitně konvertibilní na int

Výraz se převede na int a pak je přístup k elementu interpretován, jak je uvedeno v sekci Když je typ výrazu int.

Když se výraz stane implicitně konvertibilním na System.Index

Výraz se převede na System.Index, který se pak transformuje na hodnotu indexu založenou na int, jak je popsáno v https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, za předpokladu, že délka kolekce je známa v době kompilace a je rovna množství prvků v vložené matici typu primary_no_array_creation_expression. Pak je přístup k elementu interpretován podle popisu v v oddílu Když je typ výrazu int.

Když je výraz implicitně konvertibilní na System.Range

Pokud primary_no_array_creation_expression je zapisovatelná proměnná, výsledek vyhodnocení přístupu k prvku pole je hodnota, která je ekvivalentní vyvolání public Span<T> Slice (int start, int length) na instanci System.Span<T> vrácenou metodou System.Span<T> InlineArrayAsSpan v primary_no_array_creation_expression. Pro účely analýzy ref-safety je ref-safe-context/bezpečný kontext přístupu ekvivalentní stejnému pro vyvolání metody s podpisem static System.Span<T> GetSlice(ref InlineArrayType array).

Pokud je primary_no_array_creation_expression proměnnou jen pro čtení, výsledkem zhodnocení přímého přístupu k prvku pole je hodnota ekvivalentní vyvolání public ReadOnlySpan<T> Slice (int start, int length) na instanci System.ReadOnlySpan<T> vrácenou metodou System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan v primary_no_array_creation_expression. Pro účely analýzy ref-safety je ref-safe-context/a safe-context pro přístup ekvivalentní tomu samému jako při vyvolání metody s podpisem static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array).

Pokud primary_no_array_creation_expression má hodnotu, zobrazí se chyba.

Argumenty pro vyvolání metody Slice se počítají z výrazu indexu převedeného na System.Range, jak je popsáno v https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, za předpokladu, že délka kolekce je známa v době kompilace a je rovna počtu prvků v inline matici typu primary_no_array_creation_expression.

Kompilátor může vynechat volání Slice, pokud je známo v době kompilace, že start je 0 a length je menší nebo roven počtu prvků vestavěného typu pole. Kompilátor může také hlásit chybu, pokud je již v době kompilace známo, že řez přesahuje meze vnořeného pole.

Například:

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
}

Konverzace

Bude přidána nová konverze interního pole z výrazu. Vložený převod pole je standardní převod.

Existuje implicitní převod z výrazu typu vloženého pole na následující typy:

  • System.Span<T>
  • System.ReadOnlySpan<T>

Převod proměnné pouze pro čtení na System.Span<T> nebo převod hodnoty na některý z typů je chyba.

Například:

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
}

Pro účely analýzy ref-safety bezpečného kontextu převodu odpovídá bezpečnému kontextu pro vyvolání metody s podpisem static System.Span<T> Convert(ref InlineArrayType array)nebo static System.ReadOnlySpan<T> Convert(in InlineArrayType array).

Vzory seznamů

vzory seznamu nebudou podporovány pro instance vložených typů polí.

Kontrola jednoznačného přiřazení

Pravidla pro standardní určité přiřazení jsou použitelná pro proměnné, které mají typ inline pole.

Literály kolekce

Příklad vloženého typu pole je platným výrazem v spread_element.

Následující funkce nebyla dodávána v jazyce C# 12. Zůstává to otevřený návrh. Kód v tomto příkladu generuje CS9174:

Vložený typ pole je platný sestavitelný cílový typ pro výraz kolekce . Například:

Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array

Délka literálu kolekce musí odpovídat délce cílového typu inline pole. Pokud je délka literálu známá v době kompilace a neodpovídá cílové délce, zobrazí se chyba. V opačném případě dojde k vyvolání výjimky za běhu, jakmile dojde k neshodě. Přesný typ výjimky je TBD. Mezi kandidáty patří: System.NotSupportedException, System.InvalidOperationException.

Ověření aplikací InlineArrayAttribute

Kompilátor ověří následující aspekty aplikací InlineArrayAttribute:

  • Cílový typ je struktura , která není záznamem.
  • Cílový typ má pouze jedno pole.
  • Zadaná délka > 0
  • Cílová struktura nemá zadané explicitní rozložení.

Vložené prvky Pole v inicializátoru objektů

Ve výchozím nastavení nebude inicializace prvků podporována prostřednictvím initializer_target formuláře '[' argument_list ']' (viz 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;
}

Pokud však vložený typ pole explicitně definuje vhodný indexer, inicializátor objektů ho použije:

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;
    }
}

Příkaz foreach

Příkaz foreach se upraví tak, aby umožňoval použití typu vnořeného pole jako kolekce v příkazu foreach.

Například:

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;

odpovídá:

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);
}

Budeme podporovat foreach přes vložená pole, i když začíná jako omezený v async metodách kvůli zapojení typů rozsahu do překladu.

Otevřené otázky týkající se návrhu

Alternativy

Syntaxe vloženého typu pole

Gramatika na https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general se upraví takto:

array_type
    : non_array_type rank_specifier+
    ;

rank_specifier
    : '[' ','* ']'
+   | '[' constant_expression ']' 
    ;

Typ constant_expression musí být implicitně konvertibilní na typ inta hodnota musí být nenulové kladné celé číslo.

Relevantní část oddílu https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general se upraví následujícím způsobem.

Gramatická pravidla pro typy polí jsou k dispozici v https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.

Typ pole se zapíše jako non_array_type následovaný jedním nebo více rank_specifiers.

non_array_type je jakýkoli typ , který není sám o sobě array_type.

Pořadí typu pole je určeno krajním levým rank_specifier v array_type: rank_specifier označuje, že pole je pole s pořadím jedné plus počtem tokenů „,“ v rank_specifier.

Typ elementu pole typu je typ, který je výsledkem odstranění nejlevějšího rank_specifier:

  • Typ pole ve formátu T[ constant_expression ] je anonymní inline typ pole s délkou určenou constant_expression a typem prvku, který není pole T.
  • Typ pole ve formě T[ constant_expression ][R₁]...[Rₓ] je anonymní vložený typ pole s délkou určenou konstantním výrazem a typem prvku T[R₁]...[Rₓ].
  • Typ pole tvaru T[R] (kde R není constant_expression) je běžný typ pole s pořadím R a nepolovým typem prvku T.
  • Typ pole ve formátu T[R][R₁]...[Rₓ] (kde R není konstantní_výraz) je typ pole s pravidelnou strukturou s úrovní R a typem prvku T[R₁]...[Rₓ].

rank_specifiers se tedy čtou zleva doprava před posledního prvku, který není polem.

Příklad: Typ v int[][,,][,] je jednorozměrné pole, které obsahuje třírozměrná pole dvourozměrných polí int. konec příkladu

Za běhu může být hodnota běžného typu pole null nebo odkaz na instanci tohoto typu pole.

Poznámka: Podle pravidel https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariancemůže být hodnota také odkazem na kovariantní typ pole. koncová poznámka

Anonymní vložený typ pole je kompilátor syntetizovaný vložený typ pole s interní přístupností. Typ prvku musí být typ, který lze použít jako argument typu. Na rozdíl od explicitně deklarovaného vloženého typu pole nelze na anonymní vložený typ pole odkazovat podle názvu, lze na něj odkazovat pouze array_type syntaxí. V kontextu stejného programu se všechny dva array_typeoznačující vložené typy pole stejného typu prvku a stejné délky, odkazují na stejný anonymní vložený typ pole.

Kromě zajištění interní přístupnosti zabrání kompilátor použití rozhraní API, která využívají anonymní inline typy polí přes hranice sestavení. To bude dosaženo pomocí požadovaného vlastního modifikátoru, který bude použit na anonymní inline odkaz typu pole v signatuře. Přesný druh modifikátoru bude teprve určen.

Výrazy pro vytváření polí

výrazy pro tvorbu polí

array_creation_expression
    : 'new' non_array_type '[' expression_list ']' rank_specifier*
      array_initializer?
    | 'new' array_type array_initializer
    | 'new' rank_specifier array_initializer
    ;

Vzhledem k aktuální gramatikě již použití constant_expression místo expression_list má význam přidělení běžného jednorozměrného typu pole zadané délky. Proto array_creation_expression bude i nadále představovat přidělení běžného pole.

Novou formu rank_specifier lze však použít k začlenění anonymního vloženého typu pole do typu prvku přiděleného pole.

Například následující výrazy vytvoří regulární pole délky 2 s typem elementu anonymního vnořeného pole s typem elementu int a délkou 5.

new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};

Inicializátory polí

Inicializátory polí nebyly implementovány v jazyce C# 12. Tato část zůstává aktivním návrhem.

Oddíl Inicializátory pole se upraví tak, aby umožnil použití array_initializer k inicializaci vložených typů polí (není nutné měnit gramatiku).

array_initializer
    : '{' variable_initializer_list? '}'
    | '{' variable_initializer_list ',' '}'
    ;

variable_initializer_list
    : variable_initializer (',' variable_initializer)*
    ;
    
variable_initializer
    : expression
    | array_initializer
    ;

Délka vloženého pole musí být explicitně určena cílovým typem.

Například:

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

Podrobný návrh (možnost 2)

Všimněte si, že pro účely tohoto návrhu pojem "vyrovnávací paměť s pevnou velikostí" odkazuje na navrženou funkci "bezpečné vyrovnávací paměti s pevnou velikostí", nikoli na vyrovnávací paměť popsanou v https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.

V tomto návrhu nedostávají typy vyrovnávací paměti s pevnou velikostí obecně speciální zacházení jazykem. Existuje speciální syntaxe pro deklaraci členů, které představují vyrovnávací paměti s pevnou velikostí a nová pravidla týkající se využívání těchto členů. Nejsou pole z pohledu jazyka.

Gramatika pro variable_declarator v https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields se rozšíří, aby bylo možné určit velikost vyrovnávací paměti:

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 zavádí vyrovnávací paměť s pevnou velikostí daného typu prvku.

Typ prvku vyrovnávací paměti je typu , jak je specifikováno v field_declaration. Deklarátor vyrovnávací paměti s pevnou velikostí zavádí nový člen. Skládá se z identifikátoru, který tento člen pojmenovává, a konstantního výrazu, který je uzavřen v tokenech [ a ]. Konstantní výraz označuje počet prvků v členu zavedeného deklarátorem vyrovnávací paměti s pevnou velikostí. Typ konstantního výrazu musí být implicitně konvertibilní na typ inta hodnota musí být nenulové kladné celé číslo.

Prvky vyrovnávací paměti s pevnou velikostí musí být rozloženy postupně v paměti, jako by se jedná o prvky pole.

field_declaration v rozhraní s fixed_size_buffer_declarator musí mít modifikátor static.

V závislosti na situaci (podrobnosti jsou uvedeny níže) je přístup k členu vyrovnávací paměti s pevnou velikostí klasifikován jako hodnota (nikdy proměnná) System.ReadOnlySpan<S> nebo System.Span<S>, kde S je typ prvku vyrovnávací paměti s pevnou velikostí. Oba typy poskytují indexery vracející odkaz na konkrétní prvek s odpovídajícím režimem "pouze pro čtení", což brání přímému přiřazení k prvku, pokud to pravidla jazyků nedovolují.

Tím se omezí sada typů, které lze použít jako typ prvku vyrovnávací paměti s pevnou velikostí, na typy, které lze použít jako argumenty typu. Například typ ukazatele nelze použít jako typ prvku.

Výsledná instance třídy Span bude mít délku rovnu velikosti deklarované ve vyrovnávací paměti s pevnou velikostí. Vkládání indexu do rozsahu s konstantním výrazem mimo deklarované hranice vyrovnávací paměti s pevnou velikostí je chyba při kompilaci.

bezpečné kontextové hodnoty se budou rovnat bezpečnému kontextu kontejneru, stejně jako kdyby byla záložní data přístupná jako pole.

Vyrovnávací paměti s pevnou velikostí ve výrazech

Vyhledávání člena vyrovnávací paměti s pevnou velikostí pokračuje přesně stejně jako vyhledávání člena pole.

Vyrovnávací paměť s pevnou velikostí lze ve výrazu odkázat pomocí simple_name nebo member_access .

Pokud je na člen vyrovnávací paměti instance s pevnou velikostí odkazován jako na jednoduchý název, efekt je stejný jako přístup člena formuláře this.I, kde I je člen vyrovnávací paměti s pevnou velikostí. Pokud se na člen vyrovnávací paměti statické pevné velikosti odkazuje jako na jednoduchý název, efekt je stejný jako přístup člena formuláře E.I, kde I je člen vyrovnávací paměti s pevnou velikostí a E je deklarující typ.

Vyrovnávací paměti bez čtení s pevnou velikostí

V přístupu k členu ve formě E.I, pokud je E typu strukt a vyhledávání člena I v této struktuře identifikuje instanci nepouze pro čtení s pevně stanovenou velikostí, pak se E.I vyhodnotí a klasifikuje takto:

  • Pokud je E klasifikováno jako hodnota, lze E.I používat pouze jako primary_no_array_creation_expression pro přístup k elementu s indexem typu System.Index nebo typu implicitně převeditelného na int. Výsledkem přístupu k elementu je prvek člena s pevně stanovenou velikostí na zadané pozici, který je klasifikován jako hodnota.
  • V opačném případě, pokud je E klasifikována jako proměnná pouze pro čtení a výsledek výrazu je klasifikován jako hodnota typu System.ReadOnlySpan<S>, kde S je typ prvku I. Hodnotu lze použít pro přístup k prvkům člena.
  • Jinak se E klasifikuje jako zapisovatelná proměnná a výsledek výrazu se klasifikuje jako hodnota typu System.Span<S>, kde S je typ prvku I. Hodnotu lze použít pro přístup k prvkům člena.

V přístupu člena formuláře E.I, pokud E je typu třídy a členské vyhledávání I v tomto typu třídy identifikuje nečtenou instanci s pevnou velikostí, pak E.I je vyhodnocena a klasifikována jako hodnota typu System.Span<S>, kde S je typ prvku typu I.

Při přístupu člena formuláře E.I, pokud členské vyhledávání I identifikuje nečteně statický člen pevné velikosti, E.I je vyhodnocen a klasifikován jako hodnota typu System.Span<S>, kde S je typ prvku I.

Vyrovnávací paměti s pevnou velikostí jen pro čtení

Pokud field_declaration obsahuje modifikátor readonly, člen zavedený pomocí fixed_size_buffer_declarator je readonly pevné velikosti vyrovnávací paměti. Přímé přiřazení k prvkům vyrovnávací paměti jen pro čtení s pevnou velikostí může nastat pouze v konstruktoru instance, inicializačním členu nebo statickém konstruktoru stejného typu. Konkrétně přímé přiřazení k prvku vyrovnávací paměti s pevnou velikostí jen pro čtení jsou povolena pouze v následujících kontextech:

  • Pro člena instance, v konstruktorech instancí nebo iniciačním členu typu, který obsahuje deklaraci člena; pro statického člena, ve statickém konstruktoru typu, který obsahuje deklaraci člena. Jedná se také o jediné kontexty, ve kterých je platné předat prvek pevného vyrovnávacího pole jen pro čtení jako parametr out nebo ref.

Pokus přiřadit hodnotu prvku vyrovnávací paměti s pevnou velikostí jen pro čtení nebo jej předat jako parametr out nebo ref v jakémkoli jiném kontextu je chyba při kompilaci. Toho dosáhnete následujícím způsobem.

Přístup člena pro vyrovnávací paměť s pevnou velikostí jen pro čtení se vyhodnotí a klasifikuje takto:

  • V přístupu člena formuláře E.I, pokud E je typu struktury a E je klasifikována jako hodnota, lze E.I použít pouze jako primary_no_array_creation_expressionelementu, který s indexem typu System.Index nebo typu implicitně převést na int. Výsledkem přístupu elementu je prvek člena s pevnou velikostí na zadané pozici klasifikovaný jako hodnota.
  • Pokud dojde k přístupu v kontextu, kde jsou povolena přímá přiřazení elementu vyrovnávací paměti s pevnou velikostí jen pro čtení, výsledek výrazu je klasifikován jako hodnota typu System.Span<S>, kde S je typ prvku vyrovnávací paměti s pevnou velikostí. Hodnotu lze použít pro přístup k prvkům člena.
  • V opačném případě je výraz klasifikován jako hodnota typu System.ReadOnlySpan<S>, kde S je typ prvku vyrovnávací paměti s pevnou velikostí. Hodnotu lze použít pro přístup k prvkům člena.

Kontrola určitého přiřazení

Vyrovnávací paměti s pevnou velikostí nejsou předmětem kontroly určitého přiřazení a členové těchto vyrovnávacích pamětí jsou ignorováni pro účely kontroly určitého přiřazení proměnných typu struktura.

Pokud je člen vyrovnávací paměti s pevnou velikostí statický, nebo pokud je proměnná struktury, která obsahuje pevnou velikost vyrovnávací paměti, statická proměnná, proměnná instance třídy nebo prvek pole, prvky vyrovnávací paměti s pevnou velikostí se automaticky inicializují na své výchozí hodnoty. Ve všech ostatních případech není definován počáteční obsah vyrovnávací paměti s pevnou velikostí.

Metadata

Generování metadat a generování kódu

Kompilátor se bude spoléhat na nedávno přidané System.Runtime.CompilerServices.InlineArrayAttributepro kódování metadat.

Pevné vyrovnávací paměti, například následující pseudokód:

// Not valid C#
public partial class C
{
    public int buffer1[10];
    public readonly int buffer2[10];
}

budou generovány jako pole speciálně označeného typu struktury.

Ekvivalentní kód jazyka C# bude:

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);
    }
}

Skutečné zásady vytváření názvů pro typ a jeho členy budou teprve určeny. Architektura bude pravděpodobně zahrnovat sadu předdefinovaných typů vyrovnávací paměti, které pokrývají omezenou sadu velikostí vyrovnávací paměti. Pokud předdefinovaný typ neexistuje, kompilátor ho syntetizuje v modulu, který se sestavuje. Názvy vygenerovaných typů budou srozumitelné, aby podporovaly používání v jiných jazycích.

Kód vygenerovaný pro přístup, například:

public partial class C
{
    void M1(int val)
    {
        buffer1[1] = val;
    }

    int M2()
    {
        return buffer2[1];
    }
}

bude odpovídat:

public partial class C
{
    void M1(int val)
    {
        buffer.AsSpan()[1] = val;
    }

    int M2()
    {
        return buffer2.AsReadOnlySpan()[1];
    }
}
Import metadat

Když kompilátor importuje deklaraci pole typu T a všechny následující podmínky jsou splněny:

  • T je typ struktury zdobený atributem InlineArray a
  • První pole instance deklarované v T má typ Fa
  • V Tje public System.Span<F> AsSpan() a
  • V Tje public readonly System.ReadOnlySpan<T> AsReadOnlySpan() nebo public System.ReadOnlySpan<T> AsReadOnlySpan() .

Pole se bude chovat jako buffer jazyka C# s pevnou velikostí s typem prvku F. Jinak se pole bude chovat jako běžné pole typu T.

Metoda nebo soubor vlastností podobný metodickému přístupu v jazyce

Jedním z myšlenek je zacházet s těmito členy spíše jako se skupinami metod, protože nejsou automaticky hodnotou v sobě a samy o sobě, ale v případě potřeby se dají vytvořit do jedné. Takto by to fungovalo:

  • Přístupy k bezpečné vyrovnávací paměti s pevnou velikostí mají vlastní klasifikaci (stejně jako skupiny metod a lambda)
  • Je možné je indexovat přímo jako operaci jazyka (ne prostřednictvím typů span) a vytvořit proměnnou (která je jen pro čtení, pokud je vyrovnávací paměť v kontextu jen pro čtení, stejně jako pole struktury).
  • Mají implicitní převody z výrazu na Span<T> a ReadOnlySpan<T>, ale použití předchozího výrazu je chyba, pokud jsou v kontextu jen pro čtení.
  • Jejich přirozený typ je ReadOnlySpan<T>, a to, co přispívají, pokud se účastní odvození typu (např. var, best-common-type nebo obecný typ)

C/C++ buffery s pevnou velikostí

C/C++ má odlišný koncept vyrovnávacích pamětí s pevnou velikostí. Existuje například pojem "vyrovnávací paměti s pevnou velikostí a nulovou délkou", který se často používá jako způsob, jak indikovat, že data jsou "proměnlivé délky". Cílem tohoto návrhu není být schopna s tím spolupracovat.

Schůzky LDM