Dela via


Infogade matriser

Not

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta LDM-anteckningarna (språkdesignmöte).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-problem: https://github.com/dotnet/csharplang/issues/7431

Sammanfattning

Tillhandahålla en generell och säker mekanism för användning av structtyper som använder InlineArrayAttribute funktion. Ange en generell och säker mekanism för att deklarera infogade matriser i C#-klasser, structs och gränssnitt.

Obs! Tidigare versioner av den här specifikationen använde termerna "ref-safe-to-escape" och "safe-to-escape", som introducerades i Span-säkerhet funktionsspecifikationen. ECMA-standardkommittén har ändrade namnen till "ref-safe-context" respektive "safe-context". Värdena för den säkra kontexten har förfinats så att de använder "declaration-block", "function-member" och "caller-context" konsekvent. Specletarna hade använt olika formuleringar för dessa termer och även använt "safe-to-return" som synonym för "caller-context". Den här specifikationen har uppdaterats för att använda termerna i C# 7.3-standarden.

Motivation

Det här förslaget planerar att ta itu med de många begränsningarna i https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. Mer specifikt syftar det till att tillåta:

  • åtkomst till element av struct-typer som använder InlineArrayAttribute funktion;
  • deklarationen av infogade matriser för hanterade och ohanterade typer i en struct, classeller interface.

Och tillhandahålla språksäkerhetsverifiering för dem.

Detaljerad design

Nyligen har runtime lagt till InlineArrayAttribute-funktionalitet. Kort och kort kan en användare deklarera en strukturtyp som följande:

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

Runtime innehåller en särskild typlayout för Buffer-typen.

  • Storleken på typen förlängs för att rymma 10 element (talet kommer från attributet InlineArray) av object typ (typen kommer från typen för det enda instansfältet i strukturen, _element0 i det här exemplet).
  • Det första elementet är i linje med fältet för instansen och början av strukturen.
  • Elementen anges sekventiellt i minnet som om de är element i en matris.

Runtime tillhandahåller regelbunden GC-spårning för alla element i structen.

Det här förslaget refererar till typer som denna som "infogade matristyper".

Elementen av en inbäddad matristyp kan nås via pekare eller via spanninstanser som returneras av System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan API-gränssnitt. Men varken pekarmetoden eller API:erna tillhandahåller typ- och gränskontroll som standard.

Språket ger ett typsäkert/referenssäkert sätt att komma åt element av infogade matristyper. Åtkomsten kommer att vara tidsintervallbaserad. Detta begränsar stödet till infogade matristyper med elementtyper som kan användas som ett typargument. En pekartyp kan till exempel inte användas som en elementtyp. Andra exempel på intervalltyperna.

Hämta instanser av intervalltyper för en infogad matristyp

Eftersom det finns en garanti för att det första elementet i en infogad matristyp är justerat vid typens början (inget mellanrum), använder kompilatorn följande kod för att få ett Span värde:

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

Och följande kod för att få ett ReadOnlySpan värde:

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

För att minska IL-storleken på användningsplatser bör kompilatorn kunna lägga till två generiska återanvändbara hjälpare i den privata implementeringsinformationstypen och använda dem på alla användningsplatser i samma program.

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åtkomst

-elementåtkomsten utökas för att stödja åtkomst till infogade matriselement.

En element_access består av en primary_no_array_creation_expression, följt av en "[" -token följt av en argument_listföljt av en "]" token. argument_list består av ett eller flera arguments, avgränsade med kommatecken.

element_access
    : primary_no_array_creation_expression '[' argument_list ']'
    ;

argument_list för en element_access får inte innehålla ref eller out argument.

En element_access är dynamiskt bunden (§11.3.3) om minst något av följande gäller:

  • primary_no_array_creation_expression har kompileringstidstyp dynamic.
  • Minst ett uttryck för argument_list har kompileringstidstypen dynamic och primary_no_array_creation_expression har inte någon matristyp, och primary_no_array_creation_expression har inte någon infogad matristyp eller så finns det fler än ett objekt i argumentlistan.

I det här fallet klassificerar kompilatorn element_access som ett värde av typen dynamic. Reglerna nedan för att fastställa innebörden av element_access tillämpas sedan vid körning, med körningstidstypen i stället för kompileringstidstypen för de uttryck av typen primary_no_array_creation_expression och argument_list som har typen vid kompileringstid dynamic. Om primary_no_array_creation_expression inte har kompileringstidstyp dynamicgenomgår elementåtkomsten en begränsad kompileringstidskontroll enligt beskrivningen i §11.6.5.

Om primary_no_array_creation_expression för en element_access är en array_type:s värde, är element_access en matrisåtkomst (§12.8.12.2). Om primary_no_array_creation_expression för en element_access är en variabel eller ett värde av en infogad matristyp och argument_list består av ett enda argument, är element_access en infogad matriselementåtkomst. Annars ska primary_no_array_creation_expression vara en variabel eller ett värde för en klass, struct eller gränssnittstyp som har en eller flera indexermedlemmar, i vilket fall element_access är en indexeråtkomst (§12.8.12.3).

Åtkomst till infogade matriselement

För åtkomst till element i en inline-array måste primary_no_array_creation_expression för element_access vara en variabel eller ett värde av inline-arraytypen. Dessutom tillåts inte argument_list för en infogad matriselementåtkomst att innehålla namngivna argument. argument_list måste innehålla ett enda uttryck och uttrycket måste vara

  • av typen int, eller
  • implicit konvertibel till int, eller
  • implicit konvertibel till System.Index, eller
  • implicit konvertibel till System.Range.
När uttryckstypen är int

Om primary_no_array_creation_expression är en skrivbar variabel är resultatet av utvärderingen av en infogad matriselementåtkomst en skrivbar variabel som motsvarar att anropa public ref T this[int index] { get; } med det heltalsvärdet på en instans av System.Span<T> som returneras av System.Span<T> InlineArrayAsSpan-metoden på primary_no_array_creation_expression. Vid referenssäkerhetsanalys motsvarar referenssäker kontext/säker kontext av åtkomsten samma för ett anrop av en metod med signaturen static ref T GetItem(ref InlineArrayType array). Den resulterande variabeln anses flyttbar om och endast om primary_no_array_creation_expression kan flyttas.

Om primary_no_array_creation_expression är en skrivskyddad variabel är resultatet av utvärderingen av en infogad matriselementåtkomst en skrivskyddad variabel som motsvarar att anropa public ref readonly T this[int index] { get; } med det heltalsvärdet på en instans av System.ReadOnlySpan<T> som returneras av System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan metod på primary_no_array_creation_expression. Vid referenssäkerhetsanalys motsvarar referenssäker kontext/säker kontext av åtkomsten samma för ett anrop av en metod med signaturen static ref readonly T GetItem(in InlineArrayType array). Den resulterande variabeln anses flyttbar om och endast om primary_no_array_creation_expression kan flyttas.

Om primary_no_array_creation_expression är ett värde är resultatet av utvärderingen av en infogad matriselementåtkomst ett värde som motsvarar att anropa public ref readonly T this[int index] { get; } med det heltalsvärdet på en instans av System.ReadOnlySpan<T> som returneras av System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan-metoden på primary_no_array_creation_expression. Vid referenssäkerhetsanalys motsvarar referenssäker kontext/åtkomstens säker kontext detsamma för ett anrop av en metod med signaturen static T GetItem(InlineArrayType array).

Till exempel:

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

Indexering till en infogad matris med ett konstant uttryck utanför de deklarerade infogade matrisgränsen är ett kompileringstidsfel.

När uttrycket implicit konverteras till int

Uttrycket konverteras till int och sedan tolkas elementåtkomsten enligt beskrivningen i När uttryckstypen är int avsnitt.

När uttrycket implicit konverteras till System.Index

Uttrycket konverteras till System.Index, som sedan omvandlas till ett int-baserat indexvärde enligt beskrivningen i https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, förutsatt att samlingens längd är känd vid kompileringstid och är lika med mängden element i den infogade matristypen för primary_no_array_creation_expression. Sedan tolkas elementåtkomsten enligt beskrivningen i När uttryckstypen är int avsnitt.

När uttrycket implicit konverteras till System.Range

Om primary_no_array_creation_expression är en skrivbar variabel är resultatet av utvärderingen av en infogad matriselementåtkomst ett värde som motsvarar att anropa public Span<T> Slice (int start, int length) på en instans av System.Span<T> som returneras av System.Span<T> InlineArrayAsSpan-metoden på primary_no_array_creation_expression. Vid referenssäkerhetsanalys motsvarar referenssäker kontext/säker kontext av åtkomsten samma för ett anrop av en metod med signaturen static System.Span<T> GetSlice(ref InlineArrayType array).

Om primary_no_array_creation_expression är en skrivskyddad variabel är resultatet av att utvärdera en inline-åtkomst av matriselement ett värde som motsvarar att anropa public ReadOnlySpan<T> Slice (int start, int length) på en instans av System.ReadOnlySpan<T> som returneras av System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan metoden på primary_no_array_creation_expression. Vid referenssäkerhetsanalys motsvarar referenssäker kontext/säker kontext av åtkomsten samma för ett anrop av en metod med signaturen static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array).

Om primary_no_array_creation_expression är ett värde rapporteras ett fel.

Argumenten för Slice-metodanropet beräknas från det indexuttryck som konverteras till System.Range enligt beskrivningen i https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, förutsatt att samlingens längd är känd vid kompileringstidpunkten och är lika med antalet element i inline-matristypen för primary_no_array_creation_expression.

Kompilatorn kan utelämna Slice-anropet om det vid kompileringstillfället är känt att start är 0 och length är mindre eller lika med mängden element i den infogade matristypen. Kompilatorn kan också rapportera ett fel om det vid kompilering är känt att segmenteringen överskrider gränserna för inlinematriser.

Till exempel:

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
}

Omvandlingar

En ny konvertering, en infogad matriskonvertering, från uttryck kommer att läggas till. Den infogade matriskonverteringen är en standardkonvertering.

Det finns en implicit konvertering från uttryck av en inline-matristyp till följande typer:

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

Att konvertera en skrivskyddad variabel till System.Span<T> eller konvertera ett värde till någon av typerna är dock ett fel.

Till exempel:

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
}

Vid analys av referenssäkerhet är safe-context för konverteringen ekvivalent med safe-context för ett anrop av en metod med signaturen static System.Span<T> Convert(ref InlineArrayType array)eller static System.ReadOnlySpan<T> Convert(in InlineArrayType array).

Listmönster

Listmönster stöds inte för instanser av infogade matristyper.

Bestämd tilldelningskontroll

Regelbundna bestämda tilldelningsregler gäller för variabler som har en infogad matristyp.

Samlingsliteraler

En instans av en inbyggd matristyp är ett giltigt uttryck i en spread_element.

Följande funktion levereras inte i C# 12. Det är fortfarande ett öppet förslag. Koden i det här exemplet genererar CS9174:

En inbäddad matristyp är en giltig konstruerbar samling måltyp för ett samlingsuttryck. Till exempel:

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

Längden på samlingens literal måste matcha längden på målmatristypen. Om literalens längd är känd vid kompileringstiden och den inte matchar mållängden rapporteras ett fel. Annars kommer ett undantag att kastas vid körning när diskrepansen påträffas. Den exakta undantagstypen är TBD. Några kandidater är: System.NotSupportedException, System.InvalidOperationException.

Validering av InlineArrayAttribute-program

Kompilatorn validerar följande aspekter av InlineArrayAttribute-programmen:

  • Måltypen är en icke-poster struct
  • Måltypen har bara ett fält
  • Angiven längd > 0
  • Målstrukturen har ingen explicit layout angiven

Infogade matriselement i en objektinitierare

Som standardläge stöds inte elementinitiering via initializer_target av formulär '[' argument_list ']' (se 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;
}

Men om den infogade matristypen uttryckligen definierar lämplig indexerare använder objektinitieraren den:

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-instruktionen

Foreach-instruktionen justeras för att tillåta användning av en infogad matristyp som en samling i en foreach-instruktion.

Till exempel:

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;

är motsvarande:

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

Vi kommer att stödja foreach över in-line-arrayer, även om det inledningsvis är begränsat i async metoder på grund av att span-typerna är involverade i översättningen.

Öppna designfrågor

Alternativ

Syntax för infogad matristyp

Grammatiken vid https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general justeras enligt följande:

array_type
    : non_array_type rank_specifier+
    ;

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

Typen av constant_expression måste implicit konverteras till typen intoch värdet måste vara ett positivt heltal som inte är noll.

Den relevanta delen av avsnittet https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general justeras enligt följande.

Grammatikproduktionerna för matristyper finns i https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.

En matristyp skrivs som en non_array_type följt av en eller flera rank_specifiers.

En typ utan array är en typ som inte i sig själv är en array_typ.

Rangordningen för en matristyp anges av den vänstra rank_specifier i array_type: En rank_specifier anger att matrisen är en matris med en rangordning på en plus antalet "," token i rank_specifier.

Elementtypen för en arraytyp är den typ som erhålls genom att ta bort det vänstra rank_specifier:

  • En matristyp för formuläret T[ constant_expression ] är en anonym infogad matristyp med en längd som anges av constant_expression och en elementtyp som inte är matristyp T.
  • En matristyp för formuläret T[ constant_expression ][R₁]...[Rₓ] är en anonym infogad matristyp med en längd som anges av constant_expression och en elementtyp T[R₁]...[Rₓ].
  • En matristyp för formuläret T[R] (där R inte är en constant_expression) är en vanlig matristyp med rangordning R och en icke-matriselementtyp T.
  • En matristyp för formuläret T[R][R₁]...[Rₓ] (där R inte är en constant_expression) är en vanlig matristyp med rangordning R och en elementtyp T[R₁]...[Rₓ].

I själva verket läss rank_specifiers från vänster till höger innan den sista elementtypen som inte är matris.

Exempel: Typen i int[][,,][,] är en endimensionell matris med tredimensionella matriser med tvådimensionella matriser med int. slutexempel

Vid exekvering kan ett värde av en vanlig arraytyp vara null eller en referens till en instans av den arraytypen.

Obs: Efter reglerna i https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariancekan värdet också vara en referens till en covariant matristyp. slutkommentar

En anonym inline-arraytyp är en av kompilatorn syntetiserad arraytyp med intern åtkomst. Elementtypen måste vara en typ som kan användas som ett typargument. Till skillnad från en explicit deklarerad infogad matristyp kan en anonym infogad matristyp inte refereras till med namn. Den kan endast refereras till av array_type syntax. Inom ramen för samma program refererar två array_typesom betecknar inline matristyper av samma elementtyp och med samma längd till samma anonyma inline matristyp.

Förutom intern tillgång förhindrar kompilatorn användning av API:er som använder anonyma inline-matristyper över gränserna för sammanställningar genom att använda en obligatorisk anpassad modifierare (exakt typ TBD) som tillämpas på en anonym inline-matristypreferens i signaturen.

Uttryck för skapande av matris

uttryck för att skapa matriser

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

Med tanke på den aktuella grammatiken har användningen av en constant_expression i stället för expression_list redan tänkt att allokera en vanlig endimensionell matristyp av den angivna längden. Därför fortsätter array_creation_expression att representera en allokering av en vanlig matris.

Den nya formen av rank_specifier kan dock användas för att införliva en anonym infogad matristyp i elementtypen för den allokerade matrisen.

Följande uttryck skapar till exempel en vanlig matris med längd 2 med en elementtyp av en anonym infogad matristyp med elementtyp int och längd 5:

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

Arrayinitierare

Fältinitierare implementerades inte i C# 12. Det här avsnittet är fortfarande ett aktivt förslag.

Avsnittet arrayinitierare justeras för att tillåta användning av array_initializer för att initiera interna matristyper (inga ändringar av grammatiken krävs).

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

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

Längden på den infogade matrisen måste uttryckligen anges av måltypen.

Till exempel:

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

Detaljerad design (alternativ 2)

Observera att i det här förslaget refererar termen "buffert med fast storlek" till en föreslagen "säker buffert med fast storlek" i stället för en buffert som beskrivs i https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.

I den här designen får bufferttyper med fast storlek inte allmän specialbehandling av språket. Det finns en särskild syntax för att deklarera medlemmar som representerar buffertar med fast storlek och nya regler kring användning av dessa medlemmar. De är inte fält ur språksynpunkt.

Grammatiken för variable_declarator i https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields utökas så att buffertens storlek kan anges:

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 ']'
    ;    

En fixed_size_buffer_declarator introducerar en buffert med fast storlek av en viss elementtyp.

Buffertelementtypen är den typ som anges i field_declaration. En buffertdeklarator med fast storlek introducerar en ny medlem och består av en identifierare som namnger medlemmen, följt av ett konstant uttryck som omges av [ och ] token. Konstantuttrycket anger antalet element i medlemmen som introducerades av buffertdeklaratorn med fast storlek. Typen av konstantuttryck måste implicit konverteras till typen int, och värdet måste vara ett positivt icke-noll heltal.

Elementen i en buffert med fast storlek ska anges sekventiellt i minnet som om de vore element i en matris.

En field_declaration med en fixed_size_buffer_declarator i ett gränssnitt måste ha en static modifierare.

Beroende på situationen (information anges nedan) klassificeras en åtkomst till en buffertmedlem med fast storlek som ett värde (aldrig en variabel) för antingen System.ReadOnlySpan<S> eller System.Span<S>, där S är elementtypen för bufferten med fast storlek. Båda typerna ger indexerare som returnerar en referens till ett specifikt element med lämplig "readonly-ness", vilket förhindrar direkt tilldelning till elementen när språkregler inte tillåter det.

Detta begränsar den uppsättning typer som kan användas som en buffertelementtyp med fast storlek till typer som kan användas som typargument. En pekartyp kan till exempel inte användas som en elementtyp.

Den resulterande span-instansen har en längd som är lika med den storlek som deklareras i bufferten med fast storlek. Indexering i intervallet med ett konstant uttryck utanför de deklarerade buffertgränser med fast storlek är ett kompileringstidsfel.

safe-context- för värdet är lika med safe-context för containern, precis som om säkerhetskopieringsdata användes som ett fält.

Buffertar med fast storlek i uttryck

Medlemssökning för en buffertmedlem med fast storlek fortsätter precis som medlemssökning i ett fält.

En buffert med fast storlek kan refereras till i ett uttryck med hjälp av en simple_name eller en member_access .

När en instans med en buffertmedlem med fast storlek refereras till som ett enkelt namn är effekten samma som en medlemsåtkomst för formuläret this.I, där I är buffertmedlemmen med fast storlek. När en statisk buffertmedlem med fast storlek refereras till som ett enkelt namn är effekten samma som en medlemsåtkomst för formuläret E.I, där I är buffertmedlemmen med fast storlek och E är deklareringstypen.

Skrivbara buffertar med fast storlek

Om det i en medlemsåtkomst av formen E.Ifinns E som är av en structtyp och en medlemssökning av I i denna structtyp identifierar en ej readonly-instans med fast storlek, utvärderas och klassificeras E.I på följande sätt:

  • Om E klassificeras som ett värde, kan E.I endast användas som en primary_no_array_creation_expression vid en -elementåtkomst med index av System.Index-typ eller av en typ som implicit kan konverteras till int. Resultatet av elementåtkomsten är ett medlemselement med fast storlek på den angivna positionen, klassificerat som ett värde.
  • Annars, om E klassificeras som en skrivskyddad variabel och resultatet av uttrycket klassificeras som ett värde av typen System.ReadOnlySpan<S>, där S är elementtypen för I. Värdet kan användas för att komma åt medlemmens element.
  • Annars klassificeras E som en skrivbar variabel och resultatet av uttrycket klassificeras som ett värde av typen System.Span<S>, där S är elementtypen för I. Värdet kan användas för att komma åt medlemmens element.

Om E är av klasstyp och en medlemssökning av I i den klasstypen identifierar en icke-konstant instansmedlem med fast storlek, utvärderas E.I och klassificeras som ett värde av typen System.Span<S>, där S är elementtypen för I, i en medlemsåtkomst av formen E.I.

Vid en medlemsåtkomst av formatet E.I, om medlemssökning av I identifierar en icke-readonly statisk medlem med fast storlek, då utvärderas och klassificeras E.I som ett värde av typen System.Span<S>, där S är elementtypen för I.

Skrivskyddade buffertar med fast storlek

När en field_declaration innehåller en readonly modifierare är den medlem som introduceras av fixed_size_buffer_declarator en skrivskyddad buffert med fast storlek. Direkttilldelningar till element i en skrivskyddad buffert med fast storlek kan bara ske i en instanskonstruktor, init-medlem eller statisk konstruktor av samma typ. Mer specifikt tillåts direkttilldelningar till ett element med skrivskyddad buffert med fast storlek endast i följande kontexter:

  • För en instansmedlem, i instanskonstruktorerna eller init-medlemmen av den typ som innehåller medlemsdeklarationen; för en statisk medlem, i den statiska konstruktorn av den typ som innehåller medlemsdeklarationen. Det här är också de enda kontexter där det är giltigt att skicka ett element med skrivskyddad buffert med fast storlek som en out- eller ref-parameter.

Att försöka tilldela till ett element i en skrivskyddad buffert med fast storlek eller skicka det som en out eller ref parameter i andra kontexter är ett kompileringsfel. Detta uppnås genom följande.

En medlemsåtkomst för en skrivskyddad buffert med fast storlek utvärderas och klassificeras på följande sätt:

  • Om E är av en structtyp i ett medlemsåtkomstformulär E.Ioch E klassificeras som ett värde kan E.I endast användas som en primary_no_array_creation_expression av en -elementåtkomst med index av System.Index typ eller av en typ som implicit kan konverteras till int. Resultatet av elementåtkomsten är ett medlemselement med fast storlek på den angivna positionen, klassificerat som ett värde.
  • Om åtkomst sker i en kontext där direkta tilldelningar till ett element med skrivskyddad buffert med fast storlek tillåts klassificeras resultatet av uttrycket som ett värde av typen System.Span<S>, där S är elementtypen för bufferten med fast storlek. Värdet kan användas för att komma åt medlemmens element.
  • Annars klassificeras uttrycket som ett värde av typen System.ReadOnlySpan<S>, där S är elementtypen för bufferten med fast storlek. Värdet kan användas för att komma åt medlemmens element.

Bestämd tilldelningskontroll

Buffertar med fast storlek omfattas inte av någon bestämd tilldelningskontroll, och buffertmedlemmar med fast storlek ignoreras i syfte att definitivt kontrollera tilldelningen av variabler av typen struct.

När en buffertmedlem med fast storlek är statisk eller den yttersta variabeln som innehåller struct för en buffertmedlem med fast storlek är en statisk variabel, en instansvariabel för en klassinstans eller ett matriselement initieras elementen i bufferten med fast storlek automatiskt till standardvärdena. I alla andra fall är det ursprungliga innehållet i en buffert med fast storlek odefinierat.

Metadata

Metadataemission och kodgenerering

Kompilatorn för metadatakodning förlitar sig på nyligen tillagda System.Runtime.CompilerServices.InlineArrayAttribute.

Buffertar med fast storlek, till exempel följande pseudokod:

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

genereras som fält av en särskilt dekorerad structtyp.

Motsvarande C#-kod är:

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

De faktiska namngivningskonventionerna för typen och dess medlemmar är TBD. Ramverket kommer sannolikt att innehålla en uppsättning fördefinierade "bufferttyper" som täcker en begränsad uppsättning buffertstorlekar. När det inte finns någon fördefinierad typ syntetiserar kompilatorn den i modulen som skapas. Namnen på de genererade typerna är "talbara" för att stödja förbrukning från andra språk.

En kod som genereras för en åtkomst som:

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

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

kommer att motsvara:

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

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

När kompilatorn importerar en fältdeklaration av typen T och följande villkor uppfylls:

  • T är en strukturtyp som är dekorerad med attributet InlineArray och
  • Det första instansfältet som deklareras i T- har typen F, och
  • Det finns en public System.Span<F> AsSpan() inom Toch
  • Det finns en public readonly System.ReadOnlySpan<T> AsReadOnlySpan() eller public System.ReadOnlySpan<T> AsReadOnlySpan() i T.

fältet behandlas som C#-buffert med fast storlek med elementtypen F. Annars behandlas fältet som ett vanligt fält av typen T.

Metod- eller egenskapsgruppsliknande metod på språket

En tanke är att behandla dessa medlemmar mer som metodgrupper, eftersom de inte automatiskt är ett värde i och för sig själva, utan kan göras till en om det behövs. Så här fungerar det:

  • Säkra buffertåtkomster med fast storlek har sin egen klassificering (precis som metodgrupper och lambdas)
  • De kan indexeras direkt som en språkåtgärd (inte via intervalltyper) för att skapa en variabel (som är skrivskyddad om bufferten är i en skrivskyddad kontext, precis som fält i en struct)
  • De har implicita konverteringar från uttryck till Span<T> och ReadOnlySpan<T>, men användningen av den första är ett fel om de är i en skrivskyddad kontext.
  • Deras naturliga typ är ReadOnlySpan<T>, så det är vad de bidrar med om de deltar i typinferens (t.ex. var, best-common-type eller generic)

C/C++ buffertar med fast storlek

C/C++ har en annan uppfattning om buffertar med fast storlek. Det finns till exempel begreppet "buffertar med fast storlek med noll längd", som ofta används som ett sätt att indikera att data är "variabel längd". Det är inte ett mål för detta förslag att kunna vara kompatibel med det.

LDM-möten