Tableaux en ligne
Remarque
Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Il inclut les modifications de spécification proposées, ainsi que les informations nécessaires pendant la conception et le développement de la fonctionnalité. Ces articles sont publiés jusqu’à ce que les modifications de spécification proposées soient finalisées et incorporées dans la spécification ECMA actuelle.
Il peut y avoir des différences entre la spécification de la fonctionnalité et l’implémentation terminée. Ces différences sont prises en compte dans les notes pertinentes de la réunion de conception du langage (LDM).
Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications .
Résumé
Fournir un mécanisme à usage général et sûr pour consommer des types de structures utilisant la fonctionnalité InlineArrayAttribute. Fournissez un mécanisme universel et sécurisé pour déclarer des tableaux inline dans des classes, des structs et des interfaces C#.
Note : Les versions précédentes de cette spécification utilisaient les termes « ref-safe-to-escape » et « safe-to-escape », qui ont été introduits dans la spécification de la fonctionnalité de sécurité Span. Le comité standard ECMA a modifié les noms en « ref-safe-context » et « safe-context », respectivement. Les valeurs du contexte sécurisé ont été affinées pour utiliser de manière cohérente « declaration-block », « function-member » et « caller-context ». Les speclets avaient utilisé des formulations différentes pour ces termes, et avaient également utilisé « safe-to-return » comme synonyme de « caller-context ». Ce speclet a été mis à jour pour utiliser les termes de la norme C# 7.3.
Motivation
Cette proposition prévoit de répondre aux nombreuses limitations de https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. Plus précisément, elle vise à permettre :
- l'accès aux éléments des types struct en utilisant la fonctionnalité InlineArrayAttribute ;
- la déclaration de tableaux en ligne pour les types gérés et non gérés dans un
struct
,class
ouinterface
.
Et fournissez une vérification de sécurité linguistique pour eux.
Conception détaillée
Récemment, le runtime a ajouté la fonctionnalité InlineArrayAttribute . En bref, un utilisateur peut déclarer un type de structure comme suit :
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
Le runtime fournit une disposition de type spéciale pour le type Buffer
:
- La taille du type est étendue pour accueillir 10 éléments de type
object
(le nombre provient de l’attribut InlineArray), le type provient du type du seul champ d’instance dans la structure,_element0
dans cet exemple. - Le premier élément est aligné avec le champ d’instance et avec le début du struct
- Les éléments sont disposés séquentiellement en mémoire comme s’ils sont des éléments d’un tableau.
Le moteur d'exécution fournit un suivi GC régulier pour tous les éléments de la structure.
Cette proposition désigne ces types par « types de tableaux en ligne ».
Les éléments d'un type de tableau en ligne peuvent être accédés par des pointeurs ou par des instances de span renvoyées par System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan APIs. Toutefois, ni l’approche du pointeur, ni les API ne fournissent la vérification des types et des limites dès le départ.
Le langage fournira un moyen sûr/réf sûr d'accéder aux éléments des types de tableaux en ligne. L'accès sera basé sur les span. Cela limite la prise en charge des types de tableaux inline avec des types d’éléments qui peuvent être utilisés comme argument de type. Par exemple, un type de pointeur ne peut pas être utilisé comme type d’élément. Autres exemples de types de span.
Obtention d'instances de types de span pour un type de tableau en ligne
Étant donné qu’il existe une garantie que le premier élément d’un type de tableau inline est aligné au début du type (pas d’écart), le compilateur utilise le code suivant pour obtenir une valeur Span
:
MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
Et le code suivant pour obtenir une valeur ReadOnlySpan
:
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
Pour réduire la taille de l'IL aux sites d'utilisation, le compilateur devrait pouvoir ajouter deux aides génériques réutilisables dans le type de détails d'implémentation privé et les employer sur tous les sites d'utilisation du même programme.
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);
}
Accès aux éléments
L'accès aux éléments sera étendu pour permettre l'accès aux éléments des tableaux en ligne.
Un accès aux éléments se compose d'une expression primary_no_array_creation_expression, suivie d'un jeton « [
», suivi d'une argument_list, suivie d'un jeton « ]
». Le argument_list se compose d’un ou plusieurs arguments , séparés par des virgules.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
La argument_list d’une element_access n’est pas autorisée à contenir des arguments ref
ou out
.
Un element_access est lié dynamiquement (§11.3.3) si au moins l’une des conditions suivantes est remplie :
- L'expression primary_no_array_creation_expression est de type compilatoire
dynamic
. - Au moins une expression de l'argument_list a un type de compile-time
dynamic
et l'expression primary_no_array_creation_expression n'a pas de type de tableau, et l'expression primary_no_array_creation_expression n'a pas de type de tableau en ligne ou il y a plus d'un élément dans la liste d'arguments.
Dans ce cas, le compilateur classifie l'element_access en tant que valeur de type dynamic
. Les règles ci-dessous visant à déterminer la signification de l'accès_élément sont alors appliquées au moment de l'exécution, en utilisant le type au moment de l'exécution au lieu du type au moment de la compilation des expressions primary_no_array_creation_expression et argument_list qui ont le type au moment de la compilation dynamic
. Si le primary_no_array_creation_expression n’a pas de type de compilation dynamic
, l’accès à l’élément subit une vérification limitée au moment de la compilation, comme décrit dans §11.6.5.
Si l'expression primary_no_array_creation_expression d'un accès_élément est une valeur d'un type tableau, l'accès_élément est un accès_tableau (§12.8.12.2). Si l'expression primary_no_array_creation_expression d'un accès_élément est une variable ou une valeur d'un type de tableau en ligne et que l'argument_list consiste en un seul argument, l'element_access est un accès à un élément de tableau en ligne. Sinon, le primary_no_array_creation_expression doit être une variable ou une valeur d’une classe, d’un struct ou d’un type d’interface qui a un ou plusieurs membres d’indexeur, auquel cas le element_access est un accès indexeur (§12.8.12.3).
Accès à un élément de tableau en ligne
Pour un accès à un élément d’un tableau inline, l'primary_no_array_creation_expression de l'element_access doit être une variable ou une valeur d’un type de tableau inline. En outre, la argument_list d’un accès d’élément de tableau inline n’est pas autorisée à contenir des arguments nommés. La argument_list doit contenir une seule expression et l’expression doit être
- de type
int
, ou - implicitement convertible en
int
, ou - implicitement convertible en
System.Index
, ou - implicitement convertible en
System.Range
.
Lorsque le type d’expression est int
Si primary_no_array_creation_expression est une variable inscriptible, le résultat de l'évaluation d'un accès à un élément de tableau en ligne est une variable inscriptible équivalente à l'invocation de public ref T this[int index] { get; }
avec cette valeur entière sur une instance de System.Span<T>
renvoyée par la méthode System.Span<T> InlineArrayAsSpan
sur primary_no_array_creation_expression. Pour les besoins de l'analyse ref-safe-context, le /safe-context de l'accès est équivalent à celui de l'invocation d'une méthode portant la signature static ref T GetItem(ref InlineArrayType array)
.
La variable résultante est considérée comme mobile si et seulement si primary_no_array_creation_expression est mobile.
Si primary_no_array_creation_expression est une variable en lecture seule, le résultat de l'évaluation d'un accès à un élément de tableau en ligne est une variable en lecture seule équivalente à l'invocation de public ref readonly T this[int index] { get; }
avec cette valeur entière sur une instance de System.ReadOnlySpan<T>
renvoyée par la méthode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
sur primary_no_array_creation_expression. Pour les besoins de l'analyse ref-safe-context, le /safe-context de l'accès est équivalent à celui de l'invocation d'une méthode portant la signature static ref readonly T GetItem(in InlineArrayType array)
.
La variable résultante est considérée comme mobile si et seulement si primary_no_array_creation_expression est mobile.
Si primary_no_array_creation_expression est une valeur, le résultat de l'évaluation d'un accès à un élément de tableau en ligne est une valeur équivalente à l'invocation de public ref readonly T this[int index] { get; }
avec cette valeur entière sur une instance de System.ReadOnlySpan<T>
renvoyée par la méthode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
sur primary_no_array_creation_expression. Pour les besoins de l'analyse de sécurité des ref-safe-context/safe-context de l'accès est équivalent à celui de l'invocation d'une méthode portant la signature static T GetItem(InlineArrayType array)
.
Par exemple:
[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’indexation dans un tableau inline avec une expression constante en dehors des limites déclarées du tableau inline est une erreur de temps de compilation.
Lorsque l’expression est implicitement convertible en int
L’expression est convertie en int, puis l’accès à l’élément est interprété comme décrit dans Lorsque le type d’expression est int section.
Lorsque l’expression est implicitement convertible en System.Index
L’expression est convertie en System.Index
, qui est ensuite transformée en valeur d’index int comme décrit à https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, en supposant que la longueur de la collection est connue au moment de la compilation et est égale à la quantité d’éléments du type de tableau inline de la primary_no_array_creation_expression. Ensuite, l’accès à l’élément est interprété comme décrit dans Lorsque le type d’expression est int section.
Lorsque l’expression est implicitement convertible en System.Range
Si primary_no_array_creation_expression est une variable inscriptible, le résultat de l'évaluation d'un accès à un élément de tableau en ligne est une valeur équivalente à l'invocation de public Span<T> Slice (int start, int length)
sur une instance de System.Span<T>
renvoyée par la méthode System.Span<T> InlineArrayAsSpan
sur primary_no_array_creation_expression.
Aux fins de l'analyse de la ref-safe-context/safe-context de l'accès est équivalent à celui de l'invocation d'une méthode portant la signature static System.Span<T> GetSlice(ref InlineArrayType array)
.
Si primary_no_array_creation_expression est une variable en lecture seule, le résultat de l'évaluation d'un accès à un élément de tableau en ligne est une valeur équivalente à l'invocation de public ReadOnlySpan<T> Slice (int start, int length)
sur une instance de System.ReadOnlySpan<T>
renvoyée par la méthode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan
sur primary_no_array_creation_expression.
Aux fins de l'analyse de la sécurité des références, le ref-safe-context/safe-context de l'accès est équivalent à celui de l'invocation d'une méthode portant la signature static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array)
.
Si primary_no_array_creation_expression est une valeur, une erreur est signalée.
Les arguments de l’appel de méthode Slice
sont calculés à partir de l’expression d’index convertie en System.Range
comme décrit à https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, en supposant que la longueur de la collection est connue au moment de la compilation et est égale à la quantité d’éléments dans le type de tableau inline du primary_no_array_creation_expression.
Le compilateur peut omettre l’appel Slice
s’il est connu au moment de la compilation que start
est égal à 0 et length
est inférieur ou égal à la quantité d’éléments dans le type de tableau inline. Le compilateur peut également signaler une erreur s’il est connu au moment de la compilation que le découpage sort des limites de tableau inline.
Par exemple:
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
}
Conversions
Une nouvelle conversion, une conversion de tableau inline, à partir d’une expression, sera ajoutée. La conversion des tableaux en ligne est une conversion standard.
Il existe une conversion implicite de l’expression d’un type de tableau inline vers les types suivants :
System.Span<T>
System.ReadOnlySpan<T>
Toutefois, la conversion d’une variable en lecture seule en System.Span<T>
ou la conversion d’une valeur en un type est une erreur.
Par exemple:
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
}
Dans le cadre de l’analyse de la sécurité ref, le contexte sécurisé de la conversion équivaut au contexte sécurisé pour un appel d’une méthode avec la signature static System.Span<T> Convert(ref InlineArrayType array)
ou static System.ReadOnlySpan<T> Convert(in InlineArrayType array)
.
Modèles de liste
Les motifs de liste ne seront pas pris en charge pour les instances de types de tableaux en ligne.
Vérification de l’affectation définitive
Les règles d’affectation définies régulières s’appliquent aux variables qui ont un type de tableau inline.
Littéraux de collection
Une instance d’un type de tableau inline est une expression valide dans un spread_element.
La fonctionnalité suivante n’a pas été fournie en C# 12. Il reste une proposition ouverte. Le code de cet exemple génère CS9174:
Un type de tableau en ligne est un type cible de collection constructible valide pour une expression de collection. Par exemple:
Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array
La longueur du littéral de collection doit correspondre à la longueur du type de tableau en ligne cible. Si la longueur du littéral est connue au moment de la compilation et qu’elle ne correspond pas à la longueur cible, une erreur est signalée. Dans le cas contraire, une exception sera levée à l'exécution lorsque la non-concordance sera rencontrée. Le type d’exception exact est à déterminer. Certains candidats sont : System.NotSupportedException, System.InvalidOperationException.
Validation des applications InlineArrayAttribute
Le compilateur valide les aspects suivants des applications InlineArrayAttribute :
- Le type cible est une structure sans enregistrement
- Le type cible n’a qu’un seul champ
- Longueur spécifiée > 0
- Le struct cible n’a pas de disposition explicite spécifiée
Éléments inline Array dans un initialiseur d’objet
Par défaut, l’initialisation des éléments ne sera pas prise en charge via initializer_target du formulaire '[' argument_list ']'
(voir 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;
}
Toutefois, si le type de tableau inline définit explicitement l’indexeur approprié, l’initialiseur d’objet l’utilisera :
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;
}
}
L'instruction foreach
l’instruction foreach sera ajustée pour autoriser l’utilisation d’un type de tableau inline en tant que collection dans une instruction foreach.
Par exemple:
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;
équivaut à :
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);
}
Nous supporterons foreach
sur les tableaux en ligne, même si cela commence par être restreint dans les méthodes async
en raison de l'implication des types de portée dans la traduction.
Questions ouvertes de conception
Alternatives
Syntaxe des tableaux en ligne
La grammaire à https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general sera ajustée comme suit :
array_type
: non_array_type rank_specifier+
;
rank_specifier
: '[' ','* ']'
+ | '[' constant_expression ']'
;
Le type du constant_expression doit être implicitement convertible en type int
, et la valeur doit être un entier positif non nul.
La partie pertinente de la section https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general sera ajustée comme suit.
Les productions grammaticales pour les types de tableaux sont fournies dans https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.
Un type de tableau s'écrit sous la forme d'un non_array_type suivi d'un ou plusieurs rank_specifiers.
Un type non_tableau est un type qui n'est pas lui-même un type_tableau.
Le rang d’un type de tableau est déterminé par le rank_specifier le plus à gauche dans l’array_type: un rank_specifier indique que le tableau a un rang égal à un plus le nombre de jetons «,
» dans le rank_specifier.
Le type d'élément d'un type de tableau est le type qui résulte de la suppression du spécificateur de rang le plus à gauche :
- Un type de tableau du formulaire
T[ constant_expression ]
est un type de tableau inline anonyme dont la longueur est indiquée par constant_expression et un type d’élément non matricielleT
. - Un type de tableau du formulaire
T[ constant_expression ][R₁]...[Rₓ]
est un type de tableau inline anonyme dont la longueur est indiquée par constant_expression et un type d’élémentT[R₁]...[Rₓ]
. - Un type de tableau de la forme
T[R]
(où R n’est pas un constant_expression) est un type de tableau standard avec le rangR
et un type d’élément non matricielT
. - Un type de tableau du formulaire
T[R][R₁]...[Rₓ]
(où R n’est pas un constant_expression) est un type de tableau standard avec le classementR
et un type d’élémentT[R₁]...[Rₓ]
.
En effet, les spécificateurs de rang sont lus de gauche à droite avant le type d'élément final non tableau.
Exemple : Le type dans
int[][,,][,]
est un tableau unidimensionnel de tableaux tridimensionnels de tableaux bidimensionnels deint
. fin d'exemple
Au moment de l’exécution, une valeur d’un type de tableau standard peut être null
ou une référence à une instance de ce type de tableau.
Remarque: en suivant les règles de https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariance, la valeur peut également être une référence à un type de tableau covariant. "note de fin"
Un type de tableau inline anonyme est un type de tableau inline synthétisé par le compilateur avec une accessibilité interne. Le type d’élément doit être un type qui peut être utilisé comme argument de type. Contrairement à un type de tableau inline explicitement déclaré, un type de tableau inline anonyme ne peut pas être référencé par nom, il ne peut être référencé que par array_type syntaxe. Dans le contexte d'un même programme, deux array_type désignant des types de tableau en ligne du même type d'élément et de la même longueur font référence au même type de tableau anonyme en ligne.
Outre l'accessibilité interne, le compilateur empêchera l'utilisation d'API utilisant des types de tableaux inline anonymes à travers les frontières d'assembly en utilisant un modificateur personnalisé requis (type exact à déterminer) appliqué à une référence de type tableau inline anonyme dans la signature.
Expressions de création de tableau
Expressions de création de tableau
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Étant donné la grammaire actuelle, l’utilisation d’un constant_expression à la place de l'expression_list a déjà la signification d’allouer un type de tableau unidimensionnel standard de la longueur spécifiée. Par conséquent, l'expression array_creation_expression continuera à représenter l'allocation d'un tableau normal.
Toutefois, la nouvelle forme du rank_specifier peut être utilisée pour incorporer un type de tableau inline anonyme dans le type d’élément du tableau alloué.
Par exemple, les expressions suivantes créent un tableau régulier de longueur 2 avec un type d’élément d’un type de tableau inline anonyme avec type d’élément int et longueur 5 :
new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};
Initialiseurs de tableau
Les initialiseurs de tableau n’ont pas été implémentés en C# 12. Cette section reste une proposition active.
La section sur les initialisateurs de tableaux sera modifiée pour permettre l'utilisation de array_initializer afin d'initialiser les types de tableaux en ligne (aucune modification de la grammaire n'est nécessaire).
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
La longueur du tableau inline doit être fournie explicitement par le type cible.
Par exemple:
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
Conception détaillée (option 2)
Notez que, dans le cadre de cette proposition, un terme « mémoire tampon de taille fixe » fait référence à une fonctionnalité « mémoire tampon de taille fixe sécurisée » proposée plutôt qu’à une mémoire tampon décrite à https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.
Dans cette conception, les types de mémoires tampons de taille fixe ne reçoivent pas de traitement spécial général par la langue. Il existe une syntaxe spéciale pour déclarer les membres qui représentent des tampons de taille fixe et de nouvelles règles concernant la consommation de ces membres. Ils ne sont pas des champs du point de vue du langage.
La grammaire de variable_declarator dans https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields sera étendue pour permettre de spécifier la taille de la mémoire tampon :
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 introduit un tampon de taille fixe d'un type d'élément donné.
Le type d’élément de mémoire tampon est le type spécifié dans field_declaration
. Un déclarateur de tampon de taille fixe introduit un nouveau membre et se compose d’un identificateur qui nomme le membre, suivi d’une expression constante encadrée par les jetons [
et ]
. L’expression constante indique le nombre d’éléments dans le membre introduit par ce déclarateur de mémoire tampon de taille fixe. Le type de l’expression constante doit être implicitement convertible en type int
, et la valeur doit être un entier positif non nul.
Les éléments d’une mémoire tampon de taille fixe doivent être disposés séquentiellement en mémoire, comme s’ils sont des éléments d’un tableau.
Une field_declaration avec un fixed_size_buffer_declarator dans une interface doit avoir le modificateur static
.
Selon la situation (les détails sont spécifiés ci-dessous), un accès à un membre de mémoire tampon de taille fixe est classé comme une valeur (jamais une variable) de System.ReadOnlySpan<S>
ou de System.Span<S>
, où S est le type d’élément de la mémoire tampon de taille fixe. Les deux types fournissent des indexeurs renvoyant une référence à un élément spécifique avec le « readonly-ness » approprié, ce qui empêche l'affectation directe aux éléments lorsque les règles linguistiques ne l'autorisent pas.
Cela limite l’ensemble de types qui peuvent être utilisés comme type d’élément de mémoire tampon de taille fixe aux types qui peuvent être utilisés comme arguments de type. Par exemple, un type de pointeur ne peut pas être utilisé comme type d’élément.
L'instance de plage résultante aura une longueur égale à la taille déclarée de la mémoire tampon à taille fixe. L'indexation de l'étendue avec une expression constante en dehors des limites de la mémoire tampon de taille fixe déclarée est une erreur à la compilation.
Le contexte sécurisé de la valeur sera égal au contexte sécurisé du conteneur, tout comme si les données de base étaient accessibles en tant que champ.
Tampons de taille fixe dans les expressions
La consultation d'un membre d'un tampon de taille fixe se déroule exactement comme la consultation d'un champ.
Une mémoire tampon de taille fixe peut être référencée dans une expression à l’aide d’un simple_name ou d’un member_access.
Lorsqu’un membre de mémoire tampon de taille fixe d’instance est référencé sous la forme d’un nom simple, l’effet est identique à un accès membre du formulaire this.I
, où I
est le membre de mémoire tampon de taille fixe. Lorsqu’un membre de mémoire tampon de taille fixe statique est référencé sous la forme d’un nom simple, l’effet est le même qu’un accès membre du formulaire E.I
, où I
est le membre de mémoire tampon de taille fixe et E
est le type déclarant.
Tampons de taille fixe non lisibles
Dans un accès membre de la forme E.I
, si E
est d'un type struct et qu'une recherche de membre de I
dans ce type struct identifie un membre de taille fixe à instance non exclusive, E.I
est évalué et classé comme suit :
- Si
E
est classé comme une valeur,E.I
ne peut être utilisé qu'en tant qu'expression primary_no_array_creation_expression d'un accès à un élément avec un index de typeSystem.Index
ou d'un type implicitement convertible en int. Le résultat de l'accès à l'élément est un élément du membre de taille fixe à la position spécifiée, classé comme une valeur. - Sinon, si
E
est classé comme une variable en lecture seule et que le résultat de l’expression est classé comme valeur de typeSystem.ReadOnlySpan<S>
, où S est le type d’élément deI
. La valeur peut être utilisée pour accéder aux éléments du membre. - Sinon,
E
est classé comme une variable accessible en écriture et le résultat de l’expression est classé comme valeur de typeSystem.Span<S>
, où S est le type d’élément deI
. La valeur peut être utilisée pour accéder aux éléments du membre.
Dans un accès à un membre de la forme E.I
, si E
est d'un type de classe et qu'une recherche de membre de I
dans ce type de classe identifie un membre de taille fixe à instance non lisible, E.I
est évalué et classé comme une valeur de type System.Span<S>
, où S est le type d'élément de I
.
Dans un accès membre de la forme E.I
, si la consultation membre de I
identifie un membre statique à taille fixe non lisible, E.I
est évalué et classé comme une valeur de type System.Span<S>
, où S est le type d'élément de I
.
Tampons de taille fixe en lecture seule
Lorsqu'une field_declaration comprend un modificateur readonly
, le membre introduit par le déclarateur fixed_size_buffer_declarator est un tampon de taille fixe en lecture seule.
Les affectations directes aux éléments d'un tampon à taille fixe en lecture seule ne peuvent avoir lieu que dans un constructeur d'instance, un membre init ou un constructeur statique du même type.
Plus précisément, les affectations directes à un élément de mémoire tampon de taille fixe en lecture seule sont autorisées dans les contextes suivants :
- Pour un membre d'instance, dans les constructeurs d'instance ou le membre init du type qui contient la déclaration du membre ; pour un membre statique, dans le constructeur statique du type qui contient la déclaration du membre. Il s’agit également des seuls contextes dans lesquels il est valide de passer un élément de mémoire tampon de taille fixe en lecture seule en tant que paramètre
out
ouref
.
La tentative d’affectation à un élément d’une mémoire tampon de taille fixe en lecture seule ou de la transmettre en tant que paramètre out
ou ref
dans tout autre contexte est une erreur au moment de la compilation.
Pour ce faire, procédez comme suit.
Un accès membre pour une mémoire tampon de taille fixe en lecture seule est évalué et classé comme suit :
- Dans un accès à un membre de la forme
E.I
, siE
est de type struct et queE
est classé comme valeur,E.I
ne peut être utilisé que comme expression primary_no_array_creation_expression d'un accès aux éléments avec un index de typeSystem.Index
, ou d'un type implicitement convertible en int. Le résultat de l'accès à l'élément est un élément du membre de taille fixe à la position spécifiée, classé comme une valeur. - Si l’accès se produit dans un contexte où les affectations directes à un élément de mémoire tampon de taille fixe en lecture seule sont autorisées, le résultat de l’expression est classé comme valeur de type
System.Span<S>
, où S est le type d’élément de la mémoire tampon de taille fixe. La valeur peut être utilisée pour accéder aux éléments du membre. - Sinon, l’expression est classifiée comme valeur de type
System.ReadOnlySpan<S>
, où S est le type d’élément de la mémoire tampon de taille fixe. La valeur peut être utilisée pour accéder aux éléments du membre.
Vérification de l’affectation définitive
Les mémoires tampons de taille fixe ne sont pas soumises à la vérification des affectations définitives et les membres de mémoire tampon de taille fixe sont ignorés à des fins de vérification d’affectation définitive des variables de type de struct.
Lorsqu’un membre de mémoire tampon de taille fixe est statique ou que la variable de struct la plus externe d’un membre de mémoire tampon de taille fixe est une variable statique, une variable d’instance d’une instance de classe ou un élément de tableau, les éléments de la mémoire tampon de taille fixe sont automatiquement initialisés à leurs valeurs par défaut. Dans tous les autres cas, le contenu initial d’une mémoire tampon de taille fixe n’est pas défini.
Métadonnées
Émission de métadonnées et génération de code
Pour l'encodage des métadonnées, le compilateur s'appuiera sur le System.Runtime.CompilerServices.InlineArrayAttribute
récemment ajouté.
Mémoires tampons de taille fixe comme le pseudocode suivant :
// Not valid C#
public partial class C
{
public int buffer1[10];
public readonly int buffer2[10];
}
seront émises sous forme de champs d'un type struct spécialement décoré.
Le code C# équivalent sera :
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);
}
}
Les conventions de nommage actuelles pour le type et ses membres sont à déterminer. L’infrastructure inclut probablement un ensemble de types « tampon » prédéfinis qui couvrent un ensemble limité de tailles de mémoire tampon. Lorsqu’un type prédéfini n’existe pas, le compilateur le synthétise dans le module en cours de génération. Les noms des types générés seront « compréhensibles » afin de faciliter l'utilisation dans d'autres langues.
Code généré pour un accès tel que :
public partial class C
{
void M1(int val)
{
buffer1[1] = val;
}
int M2()
{
return buffer2[1];
}
}
équivaut à :
public partial class C
{
void M1(int val)
{
buffer.AsSpan()[1] = val;
}
int M2()
{
return buffer2.AsReadOnlySpan()[1];
}
}
Importation de métadonnées
Lorsque le compilateur importe une déclaration de champ de type T et que les conditions suivantes sont remplies :
- T est un type de structure agrémenté de l'attribut
InlineArray
- Le premier champ d’instance déclaré dans T a le type Fet
- Il y a un
public System.Span<F> AsSpan()
dans T, et - Il existe un
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
ou unpublic System.ReadOnlySpan<T> AsReadOnlySpan()
dans T.
le champ sera traité comme une mémoire tampon de taille fixe C# avec le type d’élément F. Sinon, le champ sera traité comme un champ normal de type T.
Méthode ou groupe de propriétés comme approche dans le langage
Une pensée est de traiter ces membres plus comme les groupes de méthodes, dans le fait qu’ils ne sont pas automatiquement une valeur en eux-mêmes, mais peuvent être transformés en un si nécessaire. Voici comment cela fonctionne :
- Les accès sécurisés aux mémoires tampons de taille fixe ont leur propre classification (comme les groupes de méthodes et les lambdas)
- Ils peuvent être indexés directement en tant qu’opération de langage (pas par le biais de types d’étendues) pour produire une variable (qui est en lecture seule si la mémoire tampon se trouve dans un contexte en lecture seule, tout comme les champs d’un struct)
- Ils ont des conversions implicites d'expression en
Span<T>
etReadOnlySpan<T>
, mais l'utilisation de la première est une erreur s'ils sont dans un contexte de lecture seule. - Leur type naturel est
ReadOnlySpan<T>
, donc c’est ce qu’ils contribuent s’ils participent à l’inférence de type (par exemple, var, best-common-type ou generic)
Mémoires tampons de taille fixe C/C++
C/C++ a une notion différente de mémoires tampons de taille fixe. Par exemple, il existe une notion de « mémoires tampons de taille fixe de longueur zéro », qui est souvent utilisée comme moyen d’indiquer que les données sont de « longueur variable ». Ce n’est pas un objectif de cette proposition de pouvoir interagir avec cela.
Réunions 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