Matrici inline
Nota
Questo articolo è una specifica delle funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono riportate nelle pertinenti note del meeting (LDM) di progettazione linguistica .
Ulteriori informazioni sul processo di integrazione delle specifiche delle funzionalità nello standard del linguaggio C# sono disponibili nell'articolo relativo alle specifiche .
Problema del campione: https://github.com/dotnet/csharplang/issues/7431
Sommario
Fornire un meccanismo generico e sicuro per l'utilizzo di tipi di struct che usano funzionalità di InlineArrayAttribute. Fornire un meccanismo generico e sicuro per dichiarare matrici inline all'interno di classi, struct e interfacce C#.
Nota: le versioni precedenti di questa specifica usavano i termini "ref-safe-to-escape" e "safe-to-escape", introdotti nella specifica della funzionalità Span safety. Il comitato standard ECMA modificato i nomi rispettivamente in "contesto di riferimento" e "contesto sicuro". I valori del contesto sicuro sono stati perfezionati per utilizzare in modo coerente "declaration-block", "function-member" e "caller-context". Gli speclet avevano utilizzato formulazioni diverse per questi termini e impiegavano anche "sicuro da restituire" come sinonimo di "contesto del chiamante". Questo speclet è stato aggiornato per usare i termini nello standard C# 7.3.
Motivazione
Questa proposta prevede di risolvere le numerose limitazioni di https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. In particolare, mira a consentire:
- accesso agli elementi dei tipi struct utilizzando la funzionalità InlineArrayAttribute di
; - dichiarazione di matrici inline per i tipi gestiti e non gestiti in un
struct
,class
ointerface
.
E fornire loro la verifica della sicurezza del linguaggio.
Progettazione dettagliata
Recentemente, il runtime ha aggiunto la funzionalità di InlineArrayAttribute. In breve, un utente può dichiarare un tipo di struttura simile al seguente:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
Il runtime fornisce un layout di tipo speciale per il tipo di Buffer
:
- Le dimensioni del tipo vengono estese per adattarsi a 10 elementi (il numero è determinato dall'attributo InlineArray) del tipo
object
(questo tipo è determinato dal tipo dell'unico campo di istanza nella struttura,_element0
in questo esempio). - Il primo elemento è allineato al campo dell'istanza e all'inizio della struttura
- Gli elementi vengono disposti in modo sequenziale in memoria come se fossero elementi di una matrice.
Il runtime fornisce un regolare rilevamento del GC per tutti gli elementi nella struttura.
Questa proposta farà riferimento a tipi come "tipi di matrice inline".
È possibile accedere agli elementi di un tipo di matrice inline tramite puntatori o tramite istanze di span restituite dalle API System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan. Tuttavia, né l'approccio basato sui puntatori, né le API forniscono il controllo dei tipi e dei limiti.
Il linguaggio fornirà un modo sicuro per i tipi e i riferimenti per accedere agli elementi dei tipi di array inline. L'accesso sarà basato sull'intervallo. Questa limitazione riguarda il supporto ai tipi di array inline con tipi di elemento che possono essere utilizzati come argomento di tipo. Ad esempio, un tipo di puntatore non può essere utilizzato come tipo di elemento. Altri esempi dei tipi span.
Ottenimento di istanze di tipi di span per un tipo di array inline
Poiché esiste una garanzia che il primo elemento di un tipo di matrice inline sia allineato all'inizio del tipo (senza gap), il compilatore userà il codice seguente per ottenere un valore Span
:
MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
E il codice seguente per ottenere il valore "ReadOnlySpan
":
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
Per ridurre le dimensioni del codice intermedio nei punti di utilizzo, il compilatore dovrebbe essere in grado di aggiungere due helper riutilizzabili generici nel tipo di dettaglio dell'implementazione privata e usarli in tutti i siti di utilizzo nello stesso 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);
}
Accesso agli elementi
L'accesso all'elemento verrà esteso per supportare l'accesso agli elementi della matrice inline.
Un element_access è costituito da un primary_no_array_creation_expression, seguito da un token “[
”, seguito da una argument_list, seguito da un token “]
”. Il argument_list è costituito da uno o più argomenti , separati da virgole.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
Il argument_list di un element_access non può contenere argomenti ref
o out
.
Un element_access è associato dinamicamente (§11.3.3) se si verifica almeno una delle seguenti condizioni:
- L'primary_no_array_creation_expression ha un tipo in fase di compilazione
dynamic
. - Almeno un'espressione del argument_list ha un tipo in fase di compilazione
dynamic
e il primary_no_array_creation_expression non ha un tipo di matrice, e il primary_no_array_creation_expression non dispone di un tipo di matrice inline o di più elementi nell'elenco di argomenti.
In questo caso, il compilatore classifica il element_access come valore di tipo dynamic
. Le regole seguenti per determinare il significato di element_access vengono quindi applicate al momento dell'esecuzione, utilizzando il tipo di runtime invece del tipo in fase di compilazione delle espressioni primary_no_array_creation_expression e argument_list che hanno il tipo in fase di compilazione dynamic
. Se il primary_no_array_creation_expression non dispone di un tipo in fase di compilazione dynamic
, l'accesso all'elemento viene sottoposto a un controllo in fase di compilazione limitato come descritto in §11.6.5.
Se la primary_no_array_creation_expression di un element_access è un valore di array_type, il element_access è un accesso all'array (§12.8.12.2). Se il primary_no_array_creation_expression di un element_access è una variabile o un valore di un tipo di matrice inline e il argument_list è costituito da un singolo argomento, l'element_access è un accesso all'elemento di matrice inline. In caso contrario, il primary_no_array_creation_expression deve essere una variabile o un valore di una classe, struttura o tipo di interfaccia con uno o più membri dell'indicizzatore, nel qual caso il element_access è un accesso dell'indicizzatore (§12.8.12.3).
Accesso all'elemento matrice inline
Per l'accesso a un elemento di matrice inline, il primary_no_array_creation_expression del element_access deve essere una variabile o un valore di un tipo di matrice inline. Inoltre, la argument_list di un accesso a un elemento di matrice inline non può contenere argomenti con nome. Il argument_list deve contenere una singola espressione e l'espressione deve essere
- di tipo
int
, o - convertibile in modo implicito in
int
o - convertibile in modo implicito in
System.Index
o - convertibile in modo implicito in
System.Range
.
Quando il tipo di espressione è int
Se primary_no_array_creation_expression è una variabile scrivibile, il risultato della valutazione di un accesso a un elemento di matrice inline è una variabile scrivibile equivalente a richiamare public ref T this[int index] { get; }
con tale valore intero in un'istanza di System.Span<T>
restituita dal metodo System.Span<T> InlineArrayAsSpan
su primary_no_array_creation_expression. Ai fini dell'analisi della sicurezza del riferimento, il contesto ref-safe /e il contesto sicuro dell'accesso sono equivalenti a quelli di un'invocazione di un metodo con la firma static ref T GetItem(ref InlineArrayType array)
.
La variabile risultante viene considerata mobile se e solo se primary_no_array_creation_expression è mobile.
Se primary_no_array_creation_expression è una variabile readonly, il risultato della valutazione di un accesso a un elemento di matrice inline è una variabile readonly equivalente a richiamare public ref readonly T this[int index] { get; }
con tale valore intero in un'istanza di System.ReadOnlySpan<T>
restituita dal metodo System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
su primary_no_array_creation_expression. Ai fini dell'analisi della sicurezza del riferimento, il contesto ref-safe /e il contesto sicuro dell'accesso sono equivalenti a quelli di un'invocazione di un metodo con la firma static ref readonly T GetItem(in InlineArrayType array)
.
La variabile risultante viene considerata mobile se e solo se primary_no_array_creation_expression è mobile.
Se primary_no_array_creation_expression è un valore, il risultato della valutazione dell'accesso a un elemento di matrice inline è un valore equivalente a richiamare public ref readonly T this[int index] { get; }
con tale valore intero in un'istanza di System.ReadOnlySpan<T>
restituito dal metodo System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
su primary_no_array_creation_expression. Ai fini dell'analisi della sicurezza del riferimento, il contesto ref-safe /e il contesto sicuro dell'accesso sono equivalenti a quelli di un'invocazione di un metodo con la firma static T GetItem(InlineArrayType array)
.
Per esempio:
[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
}
L'indicizzazione in una matrice inline con un'espressione costante all'esterno dei limiti di matrice inline dichiarati è un errore in fase di compilazione.
Quando l'espressione è convertibile in modo implicito in int
L'espressione viene convertita in int e quindi l'accesso all'elemento viene interpretato come descritto nella sezione Quando il tipo di espressione è int
Quando l'espressione è convertibile in modo implicito in System.Index
L'espressione viene convertita in System.Index
, che viene quindi trasformata in un valore di indice int come descritto in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, presupponendo che la lunghezza della raccolta sia nota in fase di compilazione ed è uguale alla quantità di elementi nel tipo di matrice inline del primary_no_array_creation_expression. Nella sezione, l'accesso all'elemento viene quindi interpretato come descritto in
Quando l'espressione è convertibile in modo implicito in System.Range
Se primary_no_array_creation_expression è una variabile scrivibile, il risultato della valutazione dell'accesso a un elemento di matrice inline è un valore equivalente a richiamare public Span<T> Slice (int start, int length)
in un'istanza di System.Span<T>
restituita dal metodo System.Span<T> InlineArrayAsSpan
su primary_no_array_creation_expression.
Ai fini dell'analisi della sicurezza del riferimento, il contesto ref-safe /e il contesto sicuro dell'accesso sono equivalenti a quelli di un'invocazione di un metodo con la firma static System.Span<T> GetSlice(ref InlineArrayType array)
.
Se primary_no_array_creation_expression è una variabile readonly, il risultato della valutazione di un accesso a un elemento di matrice inline è un valore equivalente a richiamare public ReadOnlySpan<T> Slice (int start, int length)
in un'istanza di System.ReadOnlySpan<T>
restituita dal metodo System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
su primary_no_array_creation_expression.
Ai fini dell'analisi della sicurezza del riferimento, il contesto ref-safe /e il contesto sicuro dell'accesso sono equivalenti a quelli di un'invocazione di un metodo con la firma static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array)
.
Se primary_no_array_creation_expression è un valore, viene segnalato un errore.
Gli argomenti per la chiamata al metodo Slice
vengono calcolati dall'espressione di indice convertita in System.Range
come descritto in https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, presupponendo che la lunghezza della raccolta sia nota in fase di compilazione ed è uguale alla quantità di elementi nel tipo di matrice inline del primary_no_array_creation_expression.
Il compilatore può omettere la chiamata Slice
se è noto in fase di compilazione che start
è 0 e length
è minore o uguale alla quantità di elementi nel tipo di matrice inline. Il compilatore può anche segnalare un errore se è noto in fase di compilazione che il sezionamento esce dai limiti di matrice inline.
Per esempio:
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
}
Conversioni
Verrà aggiunta una nuova conversione, una conversione di matrice inline, da un'espressione. La conversione di matrici inline è una conversione standard .
Esiste una conversione implicita dall'espressione di un tipo di matrice inline ai tipi seguenti:
System.Span<T>
System.ReadOnlySpan<T>
Tuttavia, la conversione di una variabile readonly in System.Span<T>
o la conversione di un valore in uno dei due tipi è un errore.
Per esempio:
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
}
Ai fini dell'analisi della ref-sicurezza, il contesto sicuro della conversione è equivalente al contesto sicuro per una chiamata di un metodo con la firma static System.Span<T> Convert(ref InlineArrayType array)
o static System.ReadOnlySpan<T> Convert(in InlineArrayType array)
.
Modelli di struttura degli elenchi
gli schemi di elenco non saranno supportati per le istanze di tipi di matrice inline.
Controllo degli assegnamenti definiti
Le regole di assegnazione definite regolari sono applicabili alle variabili con un tipo di matrice inline.
Valori letterali raccolta
Un'istanza di un tipo di matrice inline è un'espressione valida in un spread_element.
La funzionalità seguente non è stata spedita in C# 12. Rimane una proposta aperta. Il codice in questo esempio genera CS9174:
Un tipo di matrice inline è una raccolta costruiscibile valida tipo di destinazione per un'espressione di raccolta . Per esempio:
Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array
La lunghezza del valore letterale della raccolta deve corrispondere alla lunghezza del tipo di matrice inline di destinazione. Se la lunghezza del valore letterale è nota in fase di compilazione e non corrisponde alla lunghezza della destinazione, viene segnalato un errore. In caso contrario, verrà generata un'eccezione in fase di esecuzione quando viene rilevata la mancata corrispondenza. Il tipo di eccezione esatto è TBD. Alcuni dei candidati sono System.NotSupportedException, System.InvalidOperationException.
Convalida delle applicazioni InlineArrayAttribute
Il compilatore convaliderà gli aspetti seguenti delle applicazioni InlineArrayAttribute:
- Il tipo di destinazione è uno struct non record
- Il tipo di destinazione ha un solo campo
- Lunghezza specificata > 0
- Lo struct di destinazione non ha un layout specificato esplicitamente
Elementi di array inline in un inizializzatore di oggetto
Per impostazione predefinita, l'inizializzazione degli elementi non sarà supportata tramite initializer_target nella forma '[' argument_list ']'
(vedere 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;
}
Tuttavia, se il tipo di matrice inline definisce in modo esplicito l'indicizzatore appropriato, l'inizializzatore di oggetti lo userà:
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;
}
}
Istruzione foreach
L'istruzione foreach verrà modificata in modo da consentire l'utilizzo di un tipo di matrice inline come raccolta in un'istruzione foreach.
Per esempio:
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;
equivale a:
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);
}
Supporteremo foreach
per gli array inline, anche se inizialmente è limitato nei metodi async
a causa del coinvolgimento dei tipi span nella traduzione.
Domande di progettazione aperte
Alternative
Sintassi del tipo di matrice inline
La grammatica in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general verrà modificata nel modo seguente:
array_type
: non_array_type rank_specifier+
;
rank_specifier
: '[' ','* ']'
+ | '[' constant_expression ']'
;
Il tipo del constant_expression deve essere convertibile in modo implicito nel tipo int
e il valore deve essere un numero intero positivo diverso da zero.
La parte pertinente della sezione https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general verrà modificata nel modo seguente.
Le produzioni grammaticali per i tipi di matrice vengono fornite in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.
Un tipo di matrice viene scritto come non_array_type seguito da uno o più rank_specifiers.
Un non_array_type è qualsiasi tipo che non sia un array_type.
Il rango di un tipo di matrice viene assegnato dall'rank_specifier più a sinistra nel array_type: un rank_specifier indica che la matrice è una matrice con un rango pari a uno più il numero di token ",
" nel rank_specifier.
Il tipo di elemento di un tipo di matrice è il tipo che risulta dall'eliminazione del rank_specifier più esterno a sinistra :
- Un tipo di matrice del modulo
T[ constant_expression ]
è un tipo di matrice inline anonimo con lunghezza indicata da constant_expression e un tipo di elemento non matriceT
. - Un tipo di matrice del modulo
T[ constant_expression ][R₁]...[Rₓ]
è un tipo di matrice inline anonimo con lunghezza indicata da constant_expression e un tipo di elementoT[R₁]...[Rₓ]
. - Un tipo di matrice del modulo
T[R]
(dove R non è un constant_expression) è un tipo di matrice regolare conR
di classificazione e un tipo di elemento non matriceT
. - Un tipo di matrice del modulo
T[R][R₁]...[Rₓ]
(dove R non è un constant_expression) è un tipo di matrice regolare conR
di classificazione e un tipo di elementoT[R₁]...[Rₓ]
.
In effetti, i rank_specifiervengono letti da sinistra a destra prima di il tipo di elemento non matrice finale.
esempio: il tipo in
int[][,,][,]
è una matrice unidimensionale di matrici tridimensionali di matrici bidimensionali diint
. esempio finale
In fase di esecuzione, un valore di un tipo di matrice regolare può essere null
o un riferimento a un'istanza di tale tipo di matrice.
Nota: seguendo le regole di https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariance, il valore può anche essere un riferimento a un tipo di matrice covariante. nota finale
Un tipo di matrice inline anonimo è un tipo di matrice inline sintetizzato dal compilatore con accessibilità interna. Il tipo di elemento deve essere un tipo che può essere utilizzato come argomento di tipo. A differenza di un tipo di array inline dichiarato esplicitamente, un tipo di array inline anonimo non può essere richiamato per nome, ma solo attraverso la sintassi array_type. Nel contesto dello stesso programma, due qualsiasi array_type, che denotano tipi di matrice inline dello stesso tipo di elemento e della stessa lunghezza, fanno riferimento allo stesso tipo di matrice inline anonima.
Oltre all'accessibilità interna, il compilatore impedirà il consumo di API che utilizzano tipi di array inline anonimi attraverso i confini dell'assembly, utilizzando un modificatore personalizzato obbligatorio (tipo esatto da determinare) applicato a un riferimento al tipo di array inline anonimo nella firma.
Espressioni di creazione di matrici
espressioni di creazione di matrici
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Data la grammatica corrente, l'uso di un constant_expression al posto del expression_list ha già significato di allocare un tipo di matrice unidimensionale regolare della lunghezza specificata. Pertanto, array_creation_expression continuerà a rappresentare l'allocazione di un array regolare.
Tuttavia, è possibile usare la nuova forma del rank_specifier per incorporare un tipo di matrice inline anonima nel tipo di elemento della matrice allocata.
Ad esempio, le espressioni seguenti creano una matrice regolare di lunghezza 2 con un tipo di elemento di un tipo di matrice inline anonima con tipo di elemento int e lunghezza 5:
new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};
Inizializzatori di matrice
Gli inizializzatori di matrice non sono stati implementati in C# 12. Questa sezione rimane una proposta attiva.
La sezione inizializzatori di matrice
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
La lunghezza della matrice inline deve essere specificata in modo esplicito dal tipo di destinazione.
Per esempio:
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
Progettazione dettagliata (opzione 2)
Si noti che ai fini di questa proposta un termine "buffer a dimensione fissa" si riferisce a una funzionalità "buffer a dimensione fissa sicura" proposta anziché a un buffer descritto in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.
In questa progettazione, i tipi di buffer a dimensione fissa non ottengono un trattamento speciale generale dal linguaggio. Esiste una sintassi speciale per dichiarare i membri che rappresentano buffer a dimensione fissa e nuove regole per l'utilizzo di tali membri. Non sono campi dal punto di vista linguistico.
La grammatica per variable_declarator in https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields verrà estesa per consentire di specificare le dimensioni del buffer:
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 ']'
;
Un fixed_size_buffer_declarator introduce un buffer a dimensione fissa di un determinato tipo di elemento.
Il tipo di elemento buffer è il tipo di specificato in field_declaration
. Un dichiaratore di buffer a dimensione fissa introduce un nuovo membro ed è costituito da un identificatore che denomina il membro, seguito da un'espressione costante racchiusa in [
e ]
token. L'espressione costante indica il numero di elementi nel membro introdotto dal dichiaratore di buffer a dimensione fissa. Il tipo dell'espressione costante deve essere convertibile in modo implicito nel tipo int
e il valore deve essere un numero intero positivo diverso da zero.
Gli elementi di un buffer a dimensione fissa devono essere disposti in modo sequenziale in memoria come se fossero elementi di una matrice.
Un field_declaration con un fixed_size_buffer_declarator in un'interfaccia deve avere un static
modificatore.
A seconda della situazione (con dettagli specificati di seguito), l'accesso a un membro del buffer a dimensione fissa è classificato come un valore (mai una variabile) di System.ReadOnlySpan<S>
o System.Span<S>
, dove S è il tipo di elemento del buffer a dimensione fissa. Entrambi i tipi forniscono indicizzatori che restituiscono un riferimento di un elemento specifico con un'appropriatezza di sola lettura, che impedisce l'assegnazione diretta agli elementi quando le regole del linguaggio non lo consentono.
Questo limita il set di tipi che possono essere usati come tipo di elemento buffer a dimensione fissa ai tipi che possono essere usati come argomenti di tipo. Ad esempio, un tipo di puntatore non può essere utilizzato come tipo di elemento.
L'istanza dello span risultante avrà una lunghezza uguale alla dimensione dichiarata nel buffer a dimensione fissa. L'indicizzazione nello spazio con un'espressione costante al di fuori dei limiti del buffer a dimensione fissa dichiarato è un errore in fase di compilazione.
Il contesto sicuro del valore sarà uguale al contesto sicuro del contenitore, proprio come se si accedesse ai dati di backup come campo.
Buffer a dimensione fissa nelle espressioni
La ricerca del membro di un buffer a dimensioni fisse procede esattamente come la ricerca del membro di un campo.
È possibile fare riferimento a un buffer a dimensione fissa in un'espressione usando un simple_name o un member_access .
Quando si fa riferimento a un membro del buffer a dimensione fissa dell'istanza come nome semplice, l'effetto è equivalente a un accesso membro nella forma this.I
, dove I
rappresenta il membro del buffer a dimensione fissa. Quando si fa riferimento a un membro del buffer a dimensione fissa statica come a un nome semplice, l'effetto è lo stesso di un accesso al membro della forma E.I
, dove I
è il membro del buffer a dimensione fissa e E
è il tipo dichiarante.
Buffer a dimensione fissa non di sola lettura
In un accesso a un membro della forma E.I
, se E
è di un tipo struct e una ricerca di membri di I
in tale tipo struct identifica un membro a dimensione fissa dell'istanza non di sola lettura, allora E.I
viene valutato e classificato come segue:
- Se
E
viene classificato come valore,E.I
può essere usato solo come primary_no_array_creation_expression di un di accesso agli elementi con indice di tipoSystem.Index
o di un tipo convertibile in modo implicito in int. Il risultato dell'accesso all'elemento è l'elemento di un membro a dimensione fissa nella posizione specificata, classificato come valore. - In caso contrario, se
E
viene classificato come variabile readonly e il risultato dell'espressione viene classificato come valore di tipoSystem.ReadOnlySpan<S>
, dove S è il tipo di elemento diI
. Il valore può essere usato per accedere agli elementi del membro. - In caso contrario,
E
viene classificato come variabile scrivibile e il risultato dell'espressione viene classificato come valore di tipoSystem.Span<S>
, dove S è il tipo di elemento diI
. Il valore può essere usato per accedere agli elementi del membro.
In un accesso a un membro del tipo E.I
, se E
è di un tipo classe e una ricerca di un membro I
in tale tipo di classe identifica un membro di dimensione fissa dell'istanza che non è di sola lettura, allora E.I
viene valutato e classificato come un valore di tipo System.Span<S>
, dove S è il tipo di elemento di I
.
In un accesso a membro della forma E.I
, se la ricerca del membro di I
identifica un membro statico a dimensione fissa non di sola lettura, allora E.I
viene valutato e classificato come un valore di tipo System.Span<S>
, dove S è il tipo di elemento di I
.
Buffer di sola lettura a dimensione fissa
Quando una dichiarazione di campo field_declaration include un modificatore readonly
, il membro introdotto dal fixed_size_buffer_declarator è un buffer di sola lettura di dimensioni fisse.
Le assegnazioni dirette agli elementi di un buffer a dimensione fissa di sola lettura possono verificarsi solo in un costruttore di istanza, membro init o costruttore statico dello stesso tipo.
In particolare, le assegnazioni dirette a un elemento del buffer a dimensione fissa di sola lettura sono consentite solo nei contesti seguenti:
- Per un membro dell'istanza, nei costruttori di istanza o nel membro init del tipo che contiene la dichiarazione del membro; per un membro statico, nel costruttore statico del tipo che contiene la dichiarazione del membro. Questi sono anche gli unici contesti in cui è valido passare un elemento di buffer a dimensione fissa di sola lettura come parametro
out
oref
.
Il tentativo di assegnare a un elemento di un buffer a dimensione fissa di sola lettura o di passarlo come parametro out
o ref
in qualsiasi altro contesto è un errore in fase di compilazione.
Questo si ottiene tramite quanto segue.
L'accesso ai membri per un buffer a dimensione fissa di sola lettura viene valutato e classificato come segue:
- Nell'accesso al membro della forma
E.I
, seE
è di un tipo struct eE
è classificato come valore, si può usareE.I
solo come primary_no_array_creation_expression di un accesso all'elemento con indice di tipoSystem.Index
o di un tipo convertibile implicitamente in int. L'esito dell'accesso all'elemento è l'elemento di un membro a dimensione fissa nella posizione specificata, classificato come valore. - Se l'accesso si verifica in un contesto in cui sono consentite assegnazioni dirette a un elemento del buffer a dimensione fissa di sola lettura, il risultato dell'espressione viene classificato come valore di tipo
System.Span<S>
, dove S è il tipo di elemento del buffer a dimensione fissa. Il valore può essere usato per accedere agli elementi del membro. - In caso contrario, l'espressione viene classificata come valore di tipo
System.ReadOnlySpan<S>
, dove S è il tipo di elemento del buffer a dimensione fissa. Il valore può essere usato per accedere agli elementi del membro.
Controllo degli assegnamenti definiti
I buffer a dimensione fissa non sono soggetti al controllo delle assegnazioni definito e i membri del buffer a dimensione fissa vengono ignorati ai fini del controllo definito dell'assegnazione delle variabili del tipo di struct.
Quando un membro del buffer a dimensione fissa è statico o la variabile struct più esterna contenente il membro del buffer a dimensione fissa è una variabile statica, una variabile di istanza di una classe o un elemento di array, gli elementi del buffer a dimensione fissa vengono inizializzati automaticamente ai loro valori predefiniti. In tutti gli altri casi, il contenuto iniziale di un buffer a dimensione fissa non è definito.
Metadati
Emissione di metadati e generazione di codice
Per la codifica dei metadati, il compilatore si affiderà al recentemente aggiunto System.Runtime.CompilerServices.InlineArrayAttribute
.
Buffer a dimensione fissa come il seguente pseudocodice:
// Not valid C#
public partial class C
{
public int buffer1[10];
public readonly int buffer2[10];
}
verranno emessi come campi di un struct appositamente decorato.
Il codice C# equivalente sarà:
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);
}
}
Le convenzioni di denominazione effettive per il tipo e i relativi membri sono TBD. Il framework includerà probabilmente un set di tipi "buffer" predefiniti che coprono un set limitato di dimensioni del buffer. Quando non esiste un tipo predefinito, il compilatore lo sintetizzerà nel modulo durante la compilazione. I nomi dei tipi generati saranno "pronunciabili" per supportare l'utilizzo da altre lingue.
Codice generato per un accesso simile al seguente:
public partial class C
{
void M1(int val)
{
buffer1[1] = val;
}
int M2()
{
return buffer2[1];
}
}
sarà equivalente a:
public partial class C
{
void M1(int val)
{
buffer.AsSpan()[1] = val;
}
int M2()
{
return buffer2.AsReadOnlySpan()[1];
}
}
Importazione dei metadati
Quando il compilatore importa una dichiarazione di campo di tipo T e vengono soddisfatte tutte le condizioni seguenti:
-
T è un tipo di struct decorato con l'attributo
InlineArray
e - Il primo campo di istanza dichiarato in T ha tipo Fe
- C'è un
public System.Span<F> AsSpan()
all'interno di T, e … - È presente un
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
o unpublic System.ReadOnlySpan<T> AsReadOnlySpan()
all'interno di T.
il campo verrà considerato come buffer a dimensione fissa C# con tipo di elemento F. In caso contrario, il campo verrà considerato come un campo regolare di tipo T.
Metodo o gruppo di proprietà simile a un approccio nel linguaggio programmativo
Un'idea potrebbe essere quella di trattare questi membri più come dei gruppi di metodi, poiché non rappresentano automaticamente un valore di per sé, ma possono diventarlo, se necessario. Ecco come funziona:
- Gli accessi al buffer a dimensione fissa sicuri hanno una propria classificazione (ad esempio, gruppi di metodi e espressioni lambda)
- Possono essere indicizzati direttamente come operazione del linguaggio di programmazione (non tramite tipi span) per produrre una variabile (che è di sola lettura se il buffer si trova in un contesto di sola lettura, proprio come i campi di una struct).
- Hanno conversioni implicite da espressione a
Span<T>
eReadOnlySpan<T>
, ma l'uso del precedente è un errore se si trovano in un contesto di sola lettura. - Il loro tipo naturale è
ReadOnlySpan<T>
, quindi è ciò che forniscono se partecipano all'inferenza del tipo (ad esempio, var, tipo-migliore-comune o generico).
Buffer a dimensioni fisse C/C++
C/C++ ha una nozione diversa di buffer a dimensione fissa. Ad esempio, esiste una nozione di "buffer a dimensione fissa di lunghezza zero", che viene spesso usato come modo per indicare che i dati sono "lunghezza variabile". Non è un obiettivo di questa proposta poter interoperare con quello.
Riunioni LDM
- 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