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
,class
ellerinterface
.
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 dynamic
genomgå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 int
och 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 matristypT
. - 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 elementtypT[R₁]...[Rₓ]
. - En matristyp för formuläret
T[R]
(där R inte är en constant_expression) är en vanlig matristyp med rangordningR
och en icke-matriselementtypT
. - En matristyp för formuläret
T[R][R₁]...[Rₓ]
(där R inte är en constant_expression) är en vanlig matristyp med rangordningR
och en elementtypT[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 medint
. 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.I
finns 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, kanE.I
endast användas som en primary_no_array_creation_expression vid en -elementåtkomst med index avSystem.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 typenSystem.ReadOnlySpan<S>
, där S är elementtypen förI
. 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 typenSystem.Span<S>
, där S är elementtypen förI
. 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
- ellerref
-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ärE.I
ochE
klassificeras som ett värde kanE.I
endast användas som en primary_no_array_creation_expression av en -elementåtkomst med index avSystem.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()
ellerpublic 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>
ochReadOnlySpan<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
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-03.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-10.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-17.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#inline-arrays-as-record-structs
C# feature specifications