Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 consignées dans les notes pertinentes de la réunion de conception linguistique (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 .
Problème de champion : https://github.com/dotnet/csharplang/issues/4334
La syntaxe d’un struct d’enregistrement est la suivante :
record_struct_declaration
: attributes? struct_modifier* 'partial'? 'record' 'struct' identifier type_parameter_list?
parameter_list? struct_interfaces? type_parameter_constraints_clause* record_struct_body
;
record_struct_body
: struct_body
| ';'
;
Les types de structure d'enregistrement sont des types de valeur, comme d'autres types de structures. Ils héritent implicitement de la classe System.ValueType
.
Les modificateurs et les membres d'une structure d'enregistrement sont soumis aux mêmes restrictions que ceux des structures (accessibilité sur le type, modificateurs sur les membres, initialisateurs de constructeur d'instance base(...)
, affectation définitive pour this
dans le constructeur, destructeurs,...). Les Record structs suivront également les mêmes règles que les structs pour les constructeurs d'instance sans paramètre et les initialisateurs de champ, mais ce document suppose que nous lèverons ces restrictions pour les structs en général.
Voir §16.4.9 Voir la spécification des constructeurs de structures sans paramètre.
Les structures d'enregistrement ne peuvent pas utiliser le modificateur ref
.
Au maximum, une seule déclaration de type partiel d'une structure d'enregistrement partielle peut fournir un parameter_list
.
La parameter_list
peut être vide.
Les paramètres de structure d’enregistrement ne peuvent pas utiliser les modificateurs ref
, out
ou this
, mais in
et params
sont autorisés.
Membres d'une structure d'enregistrement
En plus des membres déclarés dans une structure d'enregistrement, un type de structure d'enregistrement dispose de membres générés supplémentaires. Les membres sont synthétisés, sauf si un membre avec une signature « correspondante » est déclaré dans le corps de la structure d’enregistrement ou si un membre concret non virtuel accessible avec une signature « correspondante » est hérité. Deux membres sont considérés comme correspondant s'ils ont la même signature ou seraient considérés comme « hiding » dans un scénario d'héritage. Voir Signatures et surcharge §7.6. Il est erroné qu'un membre d'une structure d'enregistrement soit nommé « Clone ».
Il s'agit d'une erreur qu'un champ d'instance d'une structure de données ait un type non sécurisé.
Une structure d'enregistrement n'est pas autorisée à déclarer un destructeur.
Les membres synthétisés sont les suivants :
Membres d’égalité
Les membres d'égalité synthétisés sont similaires à ceux d'une classe d'enregistrement (Equals
pour ce type, Equals
pour le type object
, ==
et !=
opérateurs pour ce type),
à l'exception de l'absence de EqualityContract
, de contrôles de nullité ou d'héritage.
La structure d'enregistrement implémente System.IEquatable<R>
et inclut une surcharge synthétisée fortement typée de Equals(R other)
où R
est la structure d'enregistrement.
La méthode est public
.
La méthode peut être déclarée explicitement. Il s’agit d’une erreur si la déclaration explicite ne correspond pas à la signature ou à l’accessibilité attendue.
Si Equals(R other)
est défini par l’utilisateur (non synthétisé), mais GetHashCode
n’est pas, un avertissement est généré.
public readonly bool Equals(R other);
Le Equals(R)
synthétisé retourne true
si et seulement si, pour chaque champ d’instance fieldN
dans la structure d'enregistrement, la valeur de System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)
dans laquelle TN
est le type de champ est true
.
La structure d’enregistrement comprend des opérateurs ==
et !=
synthétisés, équivalents aux opérateurs déclarés comme suit :
public static bool operator==(R r1, R r2)
=> r1.Equals(r2);
public static bool operator!=(R r1, R r2)
=> !(r1 == r2);
La méthode Equals
appelée par l’opérateur ==
est la méthode Equals(R other)
spécifiée ci-dessus. L’opérateur !=
délègue à l’opérateur ==
. Il s’agit d’une erreur si les opérateurs sont déclarés explicitement.
La structure d'enregistrement inclut un remplacement généré automatiquement équivalent à une méthode déclarée comme suit :
public override readonly bool Equals(object? obj);
Il s'agit d'une erreur si le remplacement est déclaré explicitement.
Le remplacement synthétisé renvoie other is R temp && Equals(temp)
où R
est la structure record.
La structure d'enregistrement inclut un remplacement généré automatiquement équivalent à une méthode déclarée comme suit :
public override readonly int GetHashCode();
La méthode peut être déclarée explicitement.
Un avertissement est signalé si l’une des Equals(R)
et GetHashCode()
est explicitement déclarée, mais que l’autre méthode n’est pas explicite.
Le remplacement synthétisé de GetHashCode()
renvoie un résultat int
de la combinaison des valeurs de System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)
pour chaque champ d'instance fieldN
, TN
étant le type de fieldN
.
Par exemple, considérez le struct d’enregistrement suivant :
record struct R1(T1 P1, T2 P2);
Pour cette structure d'enregistrement, les membres d'égalité synthétisés seraient quelque chose comme :
struct R1 : IEquatable<R1>
{
public T1 P1 { get; set; }
public T2 P2 { get; set; }
public override bool Equals(object? obj) => obj is R1 temp && Equals(temp);
public bool Equals(R1 other)
{
return
EqualityComparer<T1>.Default.Equals(P1, other.P1) &&
EqualityComparer<T2>.Default.Equals(P2, other.P2);
}
public static bool operator==(R1 r1, R1 r2)
=> r1.Equals(r2);
public static bool operator!=(R1 r1, R1 r2)
=> !(r1 == r2);
public override int GetHashCode()
{
return Combine(
EqualityComparer<T1>.Default.GetHashCode(P1),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
Membres d'impression : PrintMembers et ToString
Le struct d’enregistrement inclut une méthode synthétisée équivalente à une méthode déclarée comme suit :
private bool PrintMembers(System.Text.StringBuilder builder);
La méthode effectue les opérations suivantes :
- pour chacun des membres imprimables de la structure d'enregistrement (champs publics non statiques et membres de propriété lisibles), ajoute le nom de ce membre suivi de « = “ suivi de la valeur du membre séparée par ” , »,
- retourne vrai si la structure d'enregistrement a des membres imprimables.
Pour un membre qui a un type valeur, nous allons convertir sa valeur en représentation sous forme de chaîne à l’aide de la méthode la plus efficace disponible pour la plateforme cible. À l’heure actuelle, cela signifie appeler ToString
avant de passer à StringBuilder.Append
.
Si les membres imprimables de l'enregistrement n'incluent pas de propriété lisible avec un non-accesseurreadonly
get
, alors le PrintMembers
synthétisé est readonly
. Il n'est pas nécessaire que les champs de l'enregistrement soient readonly
pour que la méthode PrintMembers
soit readonly
.
La méthode PrintMembers
peut être déclarée explicitement.
Il s’agit d’une erreur si la déclaration explicite ne correspond pas à la signature ou à l’accessibilité attendue.
Le struct d’enregistrement inclut une méthode synthétisée équivalente à une méthode déclarée comme suit :
public override string ToString();
Si la méthode PrintMembers
du struct d’enregistrement est readonly
, la méthode de ToString()
synthétisée est readonly
.
La méthode peut être déclarée explicitement. Il s’agit d’une erreur si la déclaration explicite ne correspond pas à la signature ou à l’accessibilité attendue.
Méthode synthétisée :
- crée une instance
StringBuilder
, - ajoute le nom de la structure d'enregistrement au constructeur, suivi de « { »,
- invoque la méthode
PrintMembers
de la structure d'enregistrement en lui donnant le constructeur, suivi de « » si le résultat est vrai, - ajoute « } »,
- retourne le contenu du builder avec
builder.ToString()
.
Par exemple, considérez le struct d’enregistrement suivant :
record struct R1(T1 P1, T2 P2);
Pour cette structure d'enregistrement, les membres d'impression synthétisés ressembleraient à quelque chose comme :
struct R1 : IEquatable<R1>
{
public T1 P1 { get; set; }
public T2 P2 { get; set; }
private bool PrintMembers(StringBuilder builder)
{
builder.Append(nameof(P1));
builder.Append(" = ");
builder.Append(this.P1); // or builder.Append(this.P1.ToString()); if P1 has a value type
builder.Append(", ");
builder.Append(nameof(P2));
builder.Append(" = ");
builder.Append(this.P2); // or builder.Append(this.P2.ToString()); if P2 has a value type
return true;
}
public override string ToString()
{
var builder = new StringBuilder();
builder.Append(nameof(R1));
builder.Append(" { ");
if (PrintMembers(builder))
builder.Append(" ");
builder.Append("}");
return builder.ToString();
}
}
Membres de la structure d'enregistrement positionnels
En plus des membres ci-dessus, les structs d’enregistrement avec une liste de paramètres (« enregistrements positionnels ») synthétisent des membres supplémentaires avec les mêmes conditions que les membres ci-dessus.
Constructeur principal
Un struct d’enregistrement a un constructeur public dont la signature correspond aux paramètres de valeur de la déclaration de type. Il s’agit du constructeur principal de ce type. C'est une erreur d'avoir un constructeur primaire et un constructeur avec la même signature déjà présents dans la structure. Si la déclaration de type n’inclut pas de liste de paramètres, aucun constructeur principal n’est généré.
record struct R1
{
public R1() { } // ok
}
record struct R2()
{
public R2() { } // error: 'R2' already defines constructor with same parameter types
}
Les déclarations de champs d'instance pour une structure de données sont autorisées à inclure des initialiseurs de variables. S’il n’existe aucun constructeur principal, les initialiseurs d’instance s’exécutent dans le cadre du constructeur sans paramètre. Sinon, au moment de l'exécution, le constructeur primaire exécute les initialisateurs d'instance apparaissant dans le corps de la structure d'enregistrement.
Si un struct d’enregistrement a un constructeur principal, tout constructeur défini par l’utilisateur doit avoir un initialiseur de constructeur this
explicite qui appelle le constructeur principal ou un constructeur déclaré explicitement.
Les paramètres du constructeur principal ainsi que les membres de la structure de registre sont disponibles dans les initialiseurs des champs d’instance ou des propriétés. Les membres d'instance situé à ces emplacements constitueraient une erreur (de la même façon dont les membres d'instance sont visibles dans les initialiseurs de constructeur habituels, mais qu'il est erroné de les utiliser), mais les paramètres du constructeur primaire seraient visibles et utilisables et masqueraient les membres. Les membres statiques peuvent également être utilisés.
Un avertissement est généré si un paramètre du constructeur principal n’est pas lu.
Les règles d'affectation définies pour les constructeurs d'instances de structures s'appliquent au constructeur primaire des structures d'enregistrement. Par exemple, voici une erreur :
record struct Pos(int X) // definite assignment error in primary constructor
{
private int x;
public int X { get { return x; } set { x = value; } } = X;
}
Propriétés
Pour chaque paramètre d'une déclaration de structure d'enregistrement, il existe un membre de propriété publique correspondant dont le nom et le type sont tirés de la déclaration du paramètre de valeur.
Pour une structure d'enregistrement :
- Une auto-propriété publique
get
etinit
est créée si la record struct a le modificateurreadonly
,get
etset
sinon. Les deux types d’accesseurs set (set
etinit
) sont considérés comme « équivalents ». Par conséquent, l'utilisateur peut déclarer une propriété uniquement initialisable à la place d'une propriété mutable synthétisée. Une propriétéabstract
héritée avec un type correspondant est substituée. Aucune propriété automatique n’est créée si le struct d’enregistrement a un champ d’instance portant le nom et le type attendus. Il s’agit d’une erreur si la propriété héritée n’a paspublic
get
etset
/init
accesseurs. Il s’agit d’une erreur si la propriété ou le champ hérité est masqué.
La propriété automatique est initialisée à la valeur du paramètre de constructeur principal correspondant. Les attributs peuvent être appliqués à la propriété auto-synthétisée et à son champ de stockage en utilisant les ciblesproperty:
oufield:
pour les attributs appliqués syntaxiquement au paramètre correspondant de la structure d'enregistrement.
Déconstruire
Une structure d'enregistrement positionnelle avec au moins un paramètre synthétise une méthode d'instance publique à retour nul appelée Deconstruct
avec une déclaration de paramètre out pour chaque paramètre de la déclaration du constructeur primaire. Chaque paramètre de la méthode Deconstruct a le même type que le paramètre correspondant de la déclaration du constructeur principal. Le corps de la méthode affecte chaque paramètre de la méthode Deconstruct à la valeur d'un membre d'instance accédant à un membre du même nom.
Si les membres d'instance accédés dans le corps ne comprennent pas de propriété avec un non-accesseurreadonly
get
, alors la méthode Deconstruct
synthétisée est readonly
.
La méthode peut être déclarée explicitement. Il s’agit d’une erreur si la déclaration explicite ne correspond pas à la signature ou à l’accessibilité attendue, ou si elle est statique.
Autoriser l'expression with
sur les structures
Il est désormais possible que le récepteur d'une expression with
soit de type struct.
À droite de l'expression with
se trouve un member_initializer_list
avec une séquence d'affectations à l'identificateur , qui doit être un champ d'instance ou une propriété accessible du type du récepteur.
Pour un récepteur avec un type de struct, le récepteur est d’abord copié, puis chaque member_initializer
est traité de la même façon qu’une affectation à un champ ou un accès aux propriétés du résultat de la conversion.
Les affectations sont traitées dans l'ordre lexical.
Améliorations apportées aux enregistrements
Autoriser record class
La syntaxe existante pour les types d’enregistrements permet record class
avec la même signification que record
:
record_declaration
: attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
Autoriser les membres positionnels définis par l’utilisateur à être des champs
Aucune propriété automatique n’est créée si l’enregistrement a ou hérite d’un champ d’instance portant le nom et le type attendus.
Autoriser les constructeurs sans paramètre et les initialiseurs de membres dans les structures
Voir la spécification des constructeurs de structures sans paramètre.
Questions ouvertes
- comment reconnaître les structs d’enregistrement dans les métadonnées ? (nous n'avons pas de méthode clone innommable à exploiter...)
Répondu
- vérifiez que nous voulons conserver la conception PrintMembers (méthode distincte retournant
bool
) (réponse : oui) - confirmer que nous n’autoriserons pas
record ref struct
(problème avec les champsIEquatable<RefStruct>
et ref) (réponse : oui) - confirmer l'implémentation des membres d'égalité. L'alternative est que
bool Equals(R other)
synthétisé,bool Equals(object? other)
et les opérateurs soient tous simplement délégués àValueType.Equals
. (réponse : oui) - vérifiez que nous voulons autoriser les initialiseurs de champ lorsqu’il existe un constructeur principal. Voulons-nous également autoriser les constructeurs de structs sans paramètre pendant que nous y sommes (le problème de l’activateur a été apparemment résolu) ? (réponse : oui, les spécifications mises à jour doivent être examinées dans LDM)
- Combien voulons-nous en dire sur la méthode
Combine
? (réponse : le moins possible) - devrions-nous interdire un constructeur défini par l’utilisateur avec une signature de constructeur de copie ? (réponse : non, il n’existe aucune notion de constructeur de copie dans la spécification des structs d’enregistrement)
- confirmez que nous voulons interdire les membres nommés « Clone ». (réponse : correct)
- Vérifiez deux fois que la logique de
Equals
synthétisée est fonctionnellement équivalente à l’implémentation du runtime (par exemple, float. NaN) (réponse : confirmée dans LDM) - Les attributs de ciblage de champ ou de propriété peuvent-ils être placés dans la liste des paramètres positionnels ? (réponse : oui, comme pour la classe record)
-
with
sur les génériques ? (réponse : hors de portée pour C# 10) -
GetHashCode
doit-il inclure un hachage du type lui-même, pour obtenir des valeurs différentes entrerecord struct S1;
etrecord struct S2;
? (réponse : non)
C# feature specifications