Inlinematrices
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de notities van de relevante Language Design Meeting (LDM) .
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Kampioensprobleem: https://github.com/dotnet/csharplang/issues/7431
Samenvatting
Bied een algemeen en veilig mechanisme voor het gebruik van structtypen die gebruikmaken van het InlineArrayAttribute-kenmerk. Een algemeen en veilig mechanisme bieden voor het declareren van inlinematrices binnen C#-klassen, structs en interfaces.
Opmerking: in eerdere versies van deze specificatie werden de termen 'ref-safe-to-escape' en 'safe-to-escape' gebruikt, die zijn geïntroduceerd in de specificatie van de Spanveiligheid functiespecificatie. Het ECMA-standaardcomité heeft de namen gewijzigd in "ref-safe-context" en "safe-context", respectievelijk. De waarden van de veilige context zijn verfijnd om 'declaration-block', 'function-member' en 'caller-context' consistent te gebruiken. De speclets hadden verschillende formuleringen gebruikt voor deze termen en gebruikten ook "safe-to-return" als synoniem voor "caller-context". Deze speclet is bijgewerkt voor gebruik van de termen in de C# 7.3-standaard.
Motivatie
Dit voorstel is van plan de vele beperkingen van https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffersaan te pakken. In het bijzonder is het erop gericht het volgende toe te staan:
- toegang tot elementen van structtypen die gebruikmaken van Functie InlineArrayAttribute;
- de declaratie van inline-arrays voor beheerde en onbeheerde typen in een
struct
,class
ofinterface
.
En geef taalveiligheidsverificatie voor hen op.
Gedetailleerd ontwerp
Onlangs is de functie InlineArrayAttribute aan de runtime toegevoegd. Kortom, een gebruiker kan een structuurtype als het volgende declareren:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
Runtime biedt een speciale indeling voor het Buffer
type:
- De grootte van het type wordt uitgebreid om 10 elementen van het type
object
te passen (het getal komt uit het InlineArray kenmerk). Het type komt van het enige veld van een instantie in de struct,_element0
in dit voorbeeld. - Het eerste element wordt uitgelijnd met het exemplaarveld en met het begin van de struct
- De elementen worden sequentieel in het geheugen ingedeeld alsof ze elementen van een matrix zijn.
Runtime verzorgt doorlopende GC-tracering voor alle elementen in de struct.
Dit voorstel verwijst naar typen als 'inline-arraytypen'.
Elementen van een inlinematrixtype kunnen worden geopend via aanwijzers of via spaninstanties die worden geretourneerd door System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan API's. Zowel de aanwijzerbenadering als de API's bieden standaard geen type- en grenzencontrole.
Taal biedt een typeveilige/ref-veilige manier voor toegang tot elementen van inlinematrixtypen. De toegang is gebaseerd op de tijdsspanne. Dit beperkt de ondersteuning voor inlinematrixtypen met elementtypen die kunnen worden gebruikt als een typeargument. Een aanwijzertype kan bijvoorbeeld niet worden gebruikt als elementtype. Andere voorbeelden van de spantypen.
Instanties van spantypen verkrijgen voor een inline array-typ
Omdat er een garantie is dat het eerste element in een inlinematrixtype wordt uitgelijnd aan het begin van het type (geen hiaat), gebruikt compiler de volgende code om een Span
waarde op te halen:
MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
En de volgende code om een ReadOnlySpan
waarde op te halen:
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
Als u de IL-grootte wilt verminderen bij het compileren van sites, moet u twee algemene herbruikbare helpers kunnen toevoegen aan het detailtype voor de privé-implementatie en deze gebruiken voor alle gebruikssites in hetzelfde programma.
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);
}
Toegang tot elementen
De elementtoegang wordt uitgebreid ter ondersteuning van toegang tot inlinematrixelementen.
Een element_access bestaat uit een primary_no_array_creation_expression, gevolgd door een token "[
" gevolgd door een argument_list, gevolgd door een "]
" token. De argument_list bestaat uit een of meer arguments, gescheiden door komma's.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
De argument_list van een element_access mag geen ref
of out
argumenten bevatten.
Een element_access is dynamisch gebonden (§11.3.3) indien ten minste een van de volgende bewaringen geldt:
- De primary_no_array_creation_expression heeft een compile-time type van
dynamic
. - Ten minste één expressie van de argument_list heeft een compileertijdtype
dynamic
en de primary_no_array_creation_expression heeft geen matrixtype, en de primary_no_array_creation_expression heeft geen inline-matrixtype of er is meer dan één item in de argumentenlijst.
In dit geval classificeert de compiler de element_access als een waarde van het type dynamic
. De onderstaande regels om de betekenis van de element_access te bepalen, worden vervolgens tijdens runtime toegepast. Hierbij wordt het runtimetype gebruikt in plaats van het compileertimetype van de primary_no_array_creation_expression en de argument_list expressies, die het compileertimetype dynamic
hebben. Als de primary_no_array_creation_expression niet het compilatietijdstype dynamic
heeft, ondergaat de elementtoegang een beperkte compilatietijdcontrole zoals beschreven in §11.6.5.
Als de primary_no_array_creation_expression van een element_access een waarde is van een array_type, is de element_access een arraytoegang (§12.8.12.2). Als de primary_no_array_creation_expression van een element_access een variabele of waarde van een inlinematrixtype is en de argument_list uit één argument bestaat, is de element_access een toegang tot inlinematrixelementen. Anders moet de primary_no_array_creation_expression een variabele of waarde zijn van een klasse, struct of interfacetype met een of meer indexers, in welk geval de element_access een indexer-toegang is (§12.8.12.3).
Toegang tot inline array-elementen
Voor toegang tot een inline-arrayelement moet de primary_no_array_creation_expression van de element_access een variabele of waarde van een inline-arraytype zijn. Bovendien mag de argument_list van toegang tot inlinematrixelementen geen benoemde argumenten bevatten. De argument_list moet één expressie bevatten en de expressie moet zijn
- van het type
int
, of - impliciet converteerbaar naar
int
of - impliciet converteerbaar naar
System.Index
of - impliciet converteerbaar naar
System.Range
.
Wanneer het expressietype int is
Als primary_no_array_creation_expression een beschrijfbare variabele is, is het resultaat van het evalueren van toegang tot een inline-arrayelement een beschrijfbare variabele die overeenkomt met het aanroepen van public ref T this[int index] { get; }
met die gehele waarde op een exemplaar van System.Span<T>
, geretourneerd door de System.Span<T> InlineArrayAsSpan
-methode op primary_no_array_creation_expression. Voor de analyse van ref-veiligheid zijn de ref-safe-context/veilige contexten van de toegang vergelijkbaar met die van een aanroep van een methode met de handtekening static ref T GetItem(ref InlineArrayType array)
.
De resulterende variabele wordt beschouwd als beweegbaar als en alleen als primary_no_array_creation_expression beweegbaar is.
Als primary_no_array_creation_expression een alleen-lezen variabele is, is het resultaat van het evalueren van toegang tot een inline-arrayelement een alleen-lezen variabele, equivalent aan het aanroepen van public ref readonly T this[int index] { get; }
met die geheel getal waarde op een exemplaar van System.ReadOnlySpan<T>
dat door de System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
-methode is geretourneerd op primary_no_array_creation_expression. Voor de analyse van ref-veiligheid zijn de ref-safe-context/ref-safe-context van de toegang gelijk aan die voor een aanroep van een methode met de handtekening static ref readonly T GetItem(in InlineArrayType array)
.
De resulterende variabele wordt beschouwd als beweegbaar als en alleen als primary_no_array_creation_expression beweegbaar is.
Als primary_no_array_creation_expression een waarde is, is het resultaat van het evalueren van inline-array elementtoegang een waarde vergelijkbaar met het aanroepen van public ref readonly T this[int index] { get; }
met die gehele getalwaarde op een exemplaar van System.ReadOnlySpan<T>
, zoals geretourneerd door de System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
-methode op primary_no_array_creation_expression. Voor de analyse van ref-veiligheid zijn de ref-safe-context/veilige context van de toegang vergelijkbaar met die van een oproep van een methode met de handtekening static T GetItem(InlineArrayType array)
.
Bijvoorbeeld:
[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
}
Indexeren in een inlinematrix met een constante expressie buiten de gedeclareerde inlinematrixgrenzen is een compilatietijdfout.
Wanneer de expressie impliciet wordt omgezet in int
De expressie wordt geconverteerd naar int en vervolgens wordt de toegang tot het element geïnterpreteerd zoals beschreven in Wanneer het expressietype int sectie is.
Wanneer de expressie impliciet kan worden omgezet in System.Index
De expressie wordt geconverteerd naar System.Index
, die vervolgens wordt getransformeerd naar een indexwaarde op basis van int, zoals beschreven in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, ervan uitgaande dat de lengte van de verzameling bekend is tijdens het compileren en gelijk is aan de hoeveelheid elementen in het inlinematrixtype van de primary_no_array_creation_expression. Vervolgens wordt de toegang tot elementen geïnterpreteerd zoals beschreven in Wanneer het expressietype int sectie is.
Wanneer de expressie impliciet kan worden omgezet in System.Range
Als primary_no_array_creation_expression een beschrijfbare variabele is, is het resultaat van het evalueren van toegang tot inlinematrixelementen een waarde die gelijk is aan het aanroepen van public Span<T> Slice (int start, int length)
op een exemplaar van System.Span<T>
geretourneerd door System.Span<T> InlineArrayAsSpan
methode op primary_no_array_creation_expression.
Voor de analyse van ref-veiligheid zijn de ref-safe-context/ref-safe-context van de toegang gelijkwaardig aan die voor een aanroep van een methode met de signatuur static System.Span<T> GetSlice(ref InlineArrayType array)
.
Als primary_no_array_creation_expression een alleen-lezen variabele is, is het resultaat van het evalueren van toegang tot een element van een inline-array een waarde die gelijk is aan het oproepen van public ReadOnlySpan<T> Slice (int start, int length)
op een exemplaar van System.ReadOnlySpan<T>
, geretourneerd door de System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
-methode op primary_no_array_creation_expression.
Voor analyse van ref-veiligheid zijn de ref-safe-context/veilige context van de toegang vergelijkbaar met die van een aanroep van een methode met de handtekening static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array)
.
Als primary_no_array_creation_expression een waarde is, wordt er een fout gerapporteerd.
De argumenten voor de aanroepmethode Slice
worden berekend op basis van de indexexpressie die is geconverteerd naar System.Range
zoals beschreven in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, ervan uitgaande dat de lengte van de verzameling bekend is tijdens het compileren en gelijk is aan de hoeveelheid elementen in het inlinematrixtype van de primary_no_array_creation_expression.
Compiler kan de Slice
aanroep weglaten als deze bekend is tijdens het compileren dat start
0 is en length
kleiner of gelijk is aan de hoeveelheid elementen in het inlinematrixtype. Compiler kan ook een fout melden als het bekend is tijdens het compileren dat segmentering buiten de inlinematrixgrenzen valt.
Bijvoorbeeld:
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
}
Conversies
Er wordt een nieuwe conversie, een inlinematrixconversie, van expressie toegevoegd. De inline-matrixconversie is een standaardconversie.
Er is een impliciete conversie van een expressie van een type inline-array naar de volgende typen:
System.Span<T>
System.ReadOnlySpan<T>
Het converteren van een alleen-lezen variabele naar System.Span<T>
of het converteren van een waarde naar een van beide typen is echter een fout.
Bijvoorbeeld:
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
}
Voor de analyse van ref-veiligheid is de veilige context van de conversie gelijk aan veilige context voor een aanroep van een methode met de handtekening static System.Span<T> Convert(ref InlineArrayType array)
, of static System.ReadOnlySpan<T> Convert(in InlineArrayType array)
.
Lijstpatronen
Lijstpatronen worden niet ondersteund voor instanties van inline-arraytypes.
Definitieve toewijzingscontrole
Reguliere definitieve toewijzingsregels zijn van toepassing op variabelen met een inlinematrixtype.
Letterlijke gegevens van verzameling
Een instantie van een inline-matrixtype is een geldige uitdrukking in een spread_element.
De volgende functie is niet verzonden in C# 12. Het blijft een open voorstel. De code in dit voorbeeld genereert CS9174-:
Een inlinematrixtype is een geldige constructibele verzameling doeltype voor een verzamelingsexpressie. Bijvoorbeeld:
Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array
De lengte van de letterlijke verzameling moet overeenkomen met de lengte van het doelmatrixtype inline. Als de lengte van de letterlijke waarde bekend is tijdens het compileren en deze niet overeenkomt met de doellengte, wordt een fout gerapporteerd. Anders zal er tijdens de uitvoertijd een uitzondering optreden zodra er een mismatch plaatsvindt. Het exacte uitzonderingstype is TBD. Sommige kandidaten zijn: System.NotSupportedException, System.InvalidOperationException.
Validatie van de InlineArrayAttribute-toepassingen
Compiler valideert de volgende aspecten van de InlineArrayAttribute-toepassingen:
- Het doeltype is een niet-record structuur
- Het doeltype heeft slechts één veld
- Opgegeven lengte > 0
- De doelstruct heeft geen expliciete indeling opgegeven
Inline Array-elementen in een object-initialisatie
Als standaard wordt de initialisatie van elementen niet ondersteund via initializer_target vorm '[' argument_list ']'
(zie ook 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;
}
Als het inlinematrixtype echter expliciet geschikte indexeerfunctie definieert, gebruikt de objectinitiitiator deze:
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;
}
}
De foreach-instructie
De foreach-instructie wordt aangepast om het gebruik van een inlinematrixtype toe te staan als een verzameling in een foreach-instructie.
Bijvoorbeeld:
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;
is gelijk aan:
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);
}
We zullen foreach
ondersteunen bij inline-arrays, zelfs als deze aanvankelijk beperkt zijn in async
-methoden vanwege de betrokkenheid van de span-typen in de vertaling.
Ontwerpvragen openen
Alternatieven
De syntaxis van inline array-type
De grammatica op https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general wordt als volgt aangepast:
array_type
: non_array_type rank_specifier+
;
rank_specifier
: '[' ','* ']'
+ | '[' constant_expression ']'
;
Het type van de constant_expression moet impliciet converteerbaar zijn naar type int
, en de waarde moet een niet-nul positief geheel getal zijn.
Het relevante deel van de https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general sectie wordt als volgt aangepast.
De grammaticaproducties voor matrixtypen zijn beschikbaar in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.
Een arraytype wordt geschreven als een non_array_type gevolgd door een of meer rank_specifiers.
Een non_array_type is een type dat zelf geen array_typeis.
De rang van een matrixtype wordt gegeven door de meest linkse rank_specifier in de array_type: een rank_specifier geeft aan dat de matrix een matrix is met een rang van één plus het aantal tokens ",
" in de rank_specifier.
Het elementtype van een matrixtype is het type dat ontstaat door het verwijderen van de meest linkse rank_specifier.
- Een matrixtype van het formulier
T[ constant_expression ]
is een anoniem inlinematrixtype met de lengte die wordt aangegeven door constant_expression en een niet-matrixelementtypeT
. - Een matrixtype van het formulier
T[ constant_expression ][R₁]...[Rₓ]
is een anoniem inlinematrixtype met de lengte die wordt aangegeven door constant_expression en een elementtypeT[R₁]...[Rₓ]
. - Een matrixtype van het formulier
T[R]
(waarbij R geen constant_expressionis) is een gewoon matrixtype met rangR
en een niet-matrixelementtypeT
. - Een matrixtype van het formulier
T[R][R₁]...[Rₓ]
(waarbij R geen constant_expressionis) is een gewoon matrixtype met rangR
en een elementtypeT[R₁]...[Rₓ]
.
In feite worden de rank_specifiers gelezen van links naar rechts voordat het uiteindelijke elementtype dat niet van de matrix is.
voorbeeld: het type in
int[][,,][,]
is een eendimensionale matrix van driedimensionale matrices van tweedimensionale matrices vanint
. einde voorbeeld
Tijdens runtime kan een waarde van een normaal matrixtype worden null
of een verwijzing naar een exemplaar van dat matrixtype.
Opmerking: volgens de regels van https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariancekan de waarde ook verwijzen naar een covariant matrixtype. eindnotitie
Een anoniem inlinematrixtype is een door de compiler gesynthetiseerd inlinematrixtype met interne toegankelijkheid. Het elementtype moet een type zijn dat kan worden gebruikt als een typeargument. In tegenstelling tot een expliciet gedeclareerd inlinematrixtype, kan er niet naar een anoniem inlinematrixtype worden verwezen met een naam, maar alleen naar array_type syntaxis. In de context van hetzelfde programma verwijzen twee array_type's die inlinematrixtypen van hetzelfde elementtype en van dezelfde lengte aangeven, naar hetzelfde anonieme inlinematrixtype.
Naast interne toegankelijkheid voorkomt de compiler het gebruik van API's die gebruikmaken van anonieme inline-arraytypen over assemblagegrenzen heen met behulp van een vereiste aangepaste wijzigingsfunctie (exact type TBD) die is toegepast op een anonieme inline-arraytypereferentie in de functiehandtekening.
Expressies voor het maken van arrays
expressies voor het maken van matrices
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Gezien de huidige grammatica betekent het gebruik van een constant_expression in plaats van de expression_list dat er een regulier enkeldimensionaal array-type van de opgegeven lengte wordt toegewezen. Daarom blijft array_creation_expression een toewijzing van een reguliere matrix vertegenwoordigen.
De nieuwe vorm van de rank_specifier kan echter worden gebruikt om een anoniem inlinematrixtype op te nemen in het elementtype van de toegewezen matrix.
Met de volgende expressies wordt bijvoorbeeld een reguliere matrix van lengte 2 gemaakt met een elementtype van een anoniem inlinematrixtype met elementtype int en lengte 5:
new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};
Initialisatie van matrices
Matrix-initialisaties zijn niet geïmplementeerd in C# 12. Deze sectie blijft een actief voorstel.
De sectie array-initialisators wordt aangepast om het gebruik van array_initializer toe te staan voor het initialiseren van inline-arraytypes (geen wijzigingen in de grammatica nodig).
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
De lengte van de inlinematrix moet expliciet worden opgegeven door het doeltype.
Bijvoorbeeld:
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
Gedetailleerd ontwerp (optie 2)
Houd er rekening mee dat voor dit voorstel een term 'buffer met vaste grootte' verwijst naar een voorgestelde "veilige buffer met vaste grootte" in plaats van naar een buffer die wordt beschreven in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.
In dit ontwerp krijgen buffertypen met vaste grootte geen algemene speciale behandeling door de taal. Er is een speciale syntaxis om leden te declareren die buffers met vaste grootte vertegenwoordigen en nieuwe regels voor het gebruik van deze leden. Ze zijn geen velden vanuit het oogpunt van de taal.
De grammatica voor variable_declarator in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields wordt uitgebreid om de grootte van de buffer op te geven:
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 ']'
;
Een fixed_size_buffer_declarator introduceert een buffer met een vaste grootte van een bepaald elementtype.
Het type bufferelement is het type dat is opgegeven in field_declaration
. Een bufferdeclaratie met een vaste grootte introduceert een nieuw lid en bestaat uit een id die het lid een naam geeft, gevolgd door een constante expressie in [
en ]
tokens. De constante expressie geeft het aantal elementen in het lid aan dat door die bufferdeclaratie voor vaste grootte is geïntroduceerd. Het type van de constante expressie moet impliciet worden omgezet naar het type int
, en de waarde moet een positief geheel getal groter dan nul zijn.
De elementen van een buffer met vaste grootte moeten opeenvolgend in het geheugen worden ingedeeld alsof het elementen van een matrix zijn.
Een field_declaration met een fixed_size_buffer_declarator in een interface moet static
modifier hebben.
Afhankelijk van de situatie (details worden hieronder opgegeven), wordt een toegang tot een bufferlid met vaste grootte geclassificeerd als een waarde (nooit een variabele) van System.ReadOnlySpan<S>
of System.Span<S>
, waarbij S het elementtype van de buffer met vaste grootte is. Beide typen bieden indexeerfuncties die een verwijzing naar een specifiek element retourneren met de juiste 'readonly-ness', waardoor directe toewijzing van de elementen wordt voorkomen wanneer taalregels dat niet toestaan.
Dit beperkt de set typen die kunnen worden gebruikt als een bufferelementtype met een vaste grootte tot typen die kunnen worden gebruikt als typeargumenten. Een aanwijzertype kan bijvoorbeeld niet worden gebruikt als elementtype.
Het resulterende spanexemplaar heeft een lengte die gelijk is aan de grootte die is vastgelegd voor de buffer met vaste grootte. Indexering in de periode met een constante expressie buiten de gedeclareerde buffergrenzen met vaste grootte is een compilatietijdfout.
De veilige context van de waarde is gelijk aan de veilige context van de container, net zoals wanneer de back-upgegevens als veld worden geopend.
Buffers met vaste grootte in expressies
Het opzoeken van een bufferlid met een vaste grootte verloopt precies zoals het opzoeken van leden van een veld.
In een expressie kan naar een buffer met een vaste grootte worden verwezen met behulp van een simple_name of een member_access.
Wanneer naar een bufferlid met een vaste grootte wordt verwezen als een eenvoudige naam, is het effect hetzelfde als een lidtoegang tot het formulier this.I
, waarbij I
het bufferlid met vaste grootte is. Wanneer naar een statisch bufferlid met vaste grootte wordt verwezen als een eenvoudige naam, is het effect hetzelfde als een lidtoegang in de vorm E.I
, waarbij I
het bufferlid met vaste grootte is en E
het declaratietype is.
Niet-alleen-lezende buffers met vaste grootte
Als bij een ledentoegang in de vorm E.I
, als E
van een structtype is en een lidzoekactie van I
in dat structtype een vast formaat lid van een niet-alleen lezen instantie identificeert, wordt E.I
als volgt geëvalueerd en geclassificeerd:
- Als
E
is geclassificeerd als een waarde, kanE.I
alleen worden gebruikt als een primary_no_array_creation_expression van een elementtoegang met index vanSystem.Index
type of van een type impliciet converteerbaar naar int. Het resultaat van de elementtoegang is een element van een lid met een vaste grootte op de opgegeven positie, geclassificeerd als een waarde. - Als
E
wordt geclassificeerd als een alleen-lezen variabele en het resultaat van de expressie wordt geclassificeerd als een waarde van het typeSystem.ReadOnlySpan<S>
, waarbij S het elementtype is vanI
. De waarde kan worden gebruikt voor toegang tot de elementen van het lid. - Anders wordt
E
geclassificeerd als een beschrijfbare variabele en wordt het resultaat van de expressie geclassificeerd als een waarde van het typeSystem.Span<S>
, waarbij S het elementtype vanI
is. De waarde kan worden gebruikt voor toegang tot de elementen van het lid.
Als in een lidtoegang van de vorm E.I
, als E
van een klassetype is en een lidzoekactie van I
in dat klassetype een niet-alleen-lezen exemplaar met vaste grootte identificeert, dan wordt E.I
geëvalueerd en geclassificeerd als een waarde van het type System.Span<S>
, waarbij S het elementtype van I
is.
In het geval van een lidtoegang van de vorm E.I
, als de opzoekactie van I
een niet-alleen-lezen statisch lid met vaste grootte identificeert, wordt E.I
geëvalueerd en geclassificeerd als een waarde van het type System.Span<S>
, waarbij S het elementtype van I
is.
Alleen-lezen buffers van vaste grootte
Wanneer een field_declaration een readonly
modifier bevat, is het lid dat door de fixed_size_buffer_declarator is geïntroduceerd, een buffer met vaste grootte die alleen-lezen is.
Directe toewijzingen aan elementen van een alleen-lezen buffer met vaste grootte kunnen alleen voorkomen in een instantieconstructor, init-lid of statische constructor in hetzelfde type.
Met name directe toewijzingen aan een element van een alleen-lezen buffer met vaste grootte zijn alleen toegestaan in de volgende contexten:
- Voor een exemplaarlid, in de instantieconstructors of init-lid van het type dat de liddeclaratie bevat; voor een statisch lid, in de statische constructor van het type dat de liddeclaratie bevat. Dit zijn ook de enige contexten waarin het geldig is om een element van een alleen-lezen buffer met vaste grootte door te geven als een
out
ofref
parameter.
Een poging om toe te wijzen aan een element van een alleen-lezen buffer met vaste grootte of deze door te geven als een out
of ref
parameter in een andere context is een compilatiefout.
Dit wordt bereikt door het volgende.
Een lidtoegang voor een alleen-lezen buffer met vaste grootte wordt als volgt geëvalueerd en geclassificeerd:
- Als
E
van een structtype is enE
als een waarde is geclassificeerd in een lidtoegang van het formulierE.I
, kanE.I
alleen worden gebruikt als een primary_no_array_creation_expression binnen een elementtoegang met een index van het typeSystem.Index
, of van een type dat impliciet converteerbaar is naar int. Het resultaat van de elementtoegang is een element van een lid met vaste grootte op de opgegeven positie, geclassificeerd als een waarde. - Als toegang plaatsvindt in een context waarin directe toewijzingen aan een element van een alleen-lezen buffer met vaste grootte zijn toegestaan, wordt het resultaat van de expressie geclassificeerd als een waarde van het type
System.Span<S>
, waarbij S het elementtype van de buffer met vaste grootte is. De waarde kan worden gebruikt voor toegang tot de elementen van het lid. - Anders wordt de expressie geclassificeerd als een waarde van het type
System.ReadOnlySpan<S>
, waarbij S het elementtype van de buffer met vaste grootte is. De waarde kan worden gebruikt voor toegang tot de elementen van het lid.
Definitieve toewijzingscontrole
Buffers met vaste grootte zijn niet onderworpen aan definitieve toewijzingscontrole en bufferleden met vaste grootte worden genegeerd voor het controleren van de structtypevariabelen.
Wanneer een bufferlid met vaste grootte statisch is of de buitenste structvariabele van een bufferlid met vaste grootte een statische variabele is, een exemplaarvariabele van een klasse-exemplaar of een matrixelement, worden de elementen van de buffer met vaste grootte automatisch geïnitialiseerd naar hun standaardwaarden. In alle andere gevallen is de initiële inhoud van een buffer met een vaste grootte niet gedefinieerd.
Metagegevens
Metagegevens verzenden en code genereren
Voor de metagegevenscoderingscompiler is afhankelijk van de onlangs toegevoegde System.Runtime.CompilerServices.InlineArrayAttribute
.
Buffers met vaste grootte, zoals de volgende pseudocode:
// Not valid C#
public partial class C
{
public int buffer1[10];
public readonly int buffer2[10];
}
wordt verzonden als velden van een speciaal ingericht structtype.
De equivalente C#-code zal zijn:
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 werkelijke naamconventies voor het type en de bijbehorende leden zijn TBD. Het framework bevat waarschijnlijk een set vooraf gedefinieerde buffertypen die betrekking hebben op een beperkte set buffergrootten. Wanneer er geen vooraf gedefinieerd type bestaat, wordt dit gesynthetiseert in de module die wordt gebouwd. Namen van de gegenereerde typen zullen 'spreekbaar' zijn zodat ze gebruikt kunnen worden door andere talen.
Een code die wordt gegenereerd voor een toegang zoals:
public partial class C
{
void M1(int val)
{
buffer1[1] = val;
}
int M2()
{
return buffer2[1];
}
}
komt overeen met:
public partial class C
{
void M1(int val)
{
buffer.AsSpan()[1] = val;
}
int M2()
{
return buffer2.AsReadOnlySpan()[1];
}
}
Metagegevens importeren
Wanneer compiler een velddeclaratie importeert van het type T en aan de volgende voorwaarden wordt voldaan:
-
T is een structtype dat is versierd met het kenmerk
InlineArray
en - Het eerste exemplaarveld dat is gedeclareerd in T- heeft het type F-en
- Er is een
public System.Span<F> AsSpan()
binnen T, en - Er is een
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
ofpublic System.ReadOnlySpan<T> AsReadOnlySpan()
in T.
het veld wordt behandeld als C#-buffer met vaste grootte met elementtype F. Anders wordt het veld behandeld als een gewoon veld van het type T.
Methode of eigenschapsgroep zoals benadering in de taal
Een gedachte is om deze leden meer als methodegroepen te behandelen, omdat ze niet automatisch een waarde in en van zichzelf zijn, maar indien nodig in één kunnen worden gemaakt. Dit werkt als volgt:
- Veilige toegang tot buffers met vaste grootte hebben hun eigen classificatie (net zoals methodegroepen en lambdas)
- Ze kunnen rechtstreeks worden geïndexeerd als een taalbewerking (niet via spantypen) om een variabele te produceren (die alleen-lezen is als de buffer zich in een leescontext bevindt, net als velden van een struct)
- Ze hebben impliciete conversies van expressies naar
Span<T>
enReadOnlySpan<T>
, maar het gebruik vanSpan<T>
is een fout als ze zich in een alleen-lezen context bevinden. - Hun natuurlijke type is
ReadOnlySpan<T>
, dus dat is wat ze bijdragen als ze deelnemen aan typedeductie (bijvoorbeeld var, best-common-type of algemeen)
C/C++ buffers met vaste grootte
C/C++ heeft een ander begrip van buffers met vaste grootte. Er is bijvoorbeeld een notie van buffers met vaste grootte nul, die vaak worden gebruikt als een manier om aan te geven dat de gegevens 'variabele lengte' zijn. Het is geen doel van dit voorstel om daarmee in contact te komen.
LDM-vergaderingen
- 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