16 Structs

16.1 Général

Les structs sont similaires aux classes dans lesquelles ils représentent des structures de données qui peuvent contenir des membres de données et des membres de fonction. Toutefois, contrairement aux classes, les structs sont des types valeur et ne nécessitent pas d’allocation de tas. Une variable d’un struct type contient directement les données du struct, tandis qu’une variable d’un type de classe contient une référence aux données, ce dernier appelé objet.

Remarque : les structs sont particulièrement utiles pour les petites structures de données qui ont une sémantique de valeur. Les nombres complexes, les points dans un système de coordonnées ou les paires clé-valeur dans un dictionnaire sont de bons exemples de structures. La clé de ces structures de données est qu’elles ont peu de membres de données, qu’elles n’ont pas besoin d’utiliser la sémantique d’héritage ou de référence, plutôt qu’elles peuvent être implémentées de manière pratique à l’aide de sémantiques de valeur où l’affectation copie la valeur au lieu de la référence. Note de fin

Comme décrit dans le §8.3.5, les types simples fournis par C#, tels que int, doubleet bool, sont, en fait, tous les types de struct.

16.2 Déclarations de struct

16.2.1 Général

Un struct_declaration est un type_declaration (§14.8) qui déclare un nouveau struct :

struct_declaration
    : non_record_struct_declaration
    | record_struct_declaration
    ;

non_record_struct_declaration
    : attributes? struct_modifier* 'ref'? 'partial'? 'struct'
      identifier type_parameter_list? struct_interfaces?
      type_parameter_constraints_clause* struct_body ';'?
    ;

record_struct_declaration
    : attributes? struct_modifier* 'partial'? 'record' 'struct'
      identifier type_parameter_list? delimited_parameter_list? struct_interfaces?
      type_parameter_constraints_clause* record_struct_body
    ;

record_struct_body
    : struct_body ';'?
    | ';'
    ;

Une struct_declaration concerne un struct non-enregistrement ou un struct d’enregistrement.

Un non_record_struct_declaration se compose d’un ensemble facultatif d’attributs (§23), suivi d’un ensemble facultatif de struct_modifiers (§16.2.2), suivi d’un modificateur facultatif ref (§16.2.3), suivi d’un modificateur partiel facultatif (§15.2.7), suivi du mot clé struct et d’un identificateur qui nomme le struct, suivi d’une spécification type_parameter_list facultative (§15.2.3), suivi d’une spécification struct_interfaces facultative (§16.2.5), suivie d’une spécification facultative type_parameter_constraints-clauses (§15.2.5), suivie d’une struct_body (§16.2.6), éventuellement suivie d’un point-virgule.

Un record_struct_declaration se compose d’un ensemble facultatif d’attributs (§23), suivi d’un ensemble facultatif de struct_modifiers (§16.2.2), suivi d’un modificateur partiel facultatif (§15.2.7), suivi du mot clé, suivi du mot clé recordstruct et d’un identificateur qui nomme le struct, suivi d’une spécification type_parameter_list facultative (§15.2.3), suivi d’un delimited_parameter_list facultatif spécification (§15.2.1), suivie d’une spécification facultative struct_interfaces (§16.2.5), suivie d’une spécification facultative type_parameter_constraints-clauses (§15.2.5), suivie d’un record_struct_body.

Une struct_declaration ne fournit pas type_parameter_constraints_clauses, sauf s’il fournit également un type_parameter_list.

Une struct_declaration qui fournit une type_parameter_list est une déclaration de struct générique. En outre, tout struct imbriqué à l’intérieur d’une déclaration de classe générique ou d’une déclaration de struct générique est lui-même une déclaration de struct générique, car les arguments de type pour le type conteneur doivent être fournis pour créer un type construit (§8.4).

Une non_record_struct_declaration qui inclut un ref modificateur n’a pas de partie struct_interfaces .

Un record_struct_declaration ayant un delimited_parameter_list déclare un struct d’enregistrement positionnel.

Au plus un seul record_struct_declaration contenant partial peut fournir un delimited_parameter_list.

Les paramètres de delimited_parameter_list ne doivent pas avoir ref, out ni this modificateurs ; toutefois, in et params les modificateurs sont autorisés. Pour un record_struct_declaration, les record_struct_bodys {}, {};et ; sont équivalents. Ils indiquent tous que les seuls membres sont ceux synthétisés par le compilateur (§16.4).

Modificateurs de struct 16.2.2

Un struct_declaration peut éventuellement inclure une séquence de struct_modifiers :

struct_modifier
    : 'new'
    | 'public'
    | 'protected'
    | 'internal'
    | 'private'
    | 'readonly'
    | unsafe_modifier   // unsafe code support
    ;

unsafe_modifier (§24.2) n’est disponible que dans le code non sécurisé (§24).

Il s’agit d’une erreur au moment de la compilation pour que le même modificateur apparaisse plusieurs fois dans une déclaration de struct.

readonlyÀ l’exception de , les modificateurs d’une déclaration de struct ont la même signification que ceux d’une déclaration de classe (§15.2.2).

Le readonly modificateur indique que le struct_declaration déclare un type dont les instances sont immuables.

Un struct en lecture seule a les contraintes suivantes :

  • Chacun de ses champs d’instance doit également être déclaré readonly.
  • Il ne doit déclarer aucun événement de type champ (§15.8.2).

Lorsqu’une instance d’un struct en lecture seule est passée à une méthode, elle this est traitée comme un argument/paramètre d’entrée, ce qui interdit l’accès en écriture à tous les champs d’instance (sauf par les constructeurs).

16.2.3 Modificateur Ref

Le ref modificateur indique que le non_record_struct_declaration déclare un type dont les instances sont allouées sur la pile d’exécution. Ces types sont appelés types de struct ref. Le ref modificateur déclare que les instances peuvent contenir des champs de type ref et ne doivent pas être copiées hors de son contexte sécurisé (§16.5.15). Les règles de détermination du contexte sûr d’un struct ref sont décrites dans le §16.5.15.

Il s’agit d’une erreur au moment de la compilation si un type de struct ref est utilisé dans l’un des contextes suivants :

  • En tant que type d’élément d’un tableau.
  • Comme type déclaré d’un champ d’une classe ou d’un struct qui n’a pas le ref modificateur.
  • En tant qu’argument de type.
  • En tant que type d’élément tuple.
  • Dans une méthode asynchrone.
  • Dans un itérateur.
  • En tant que type de récepteur pour une conversion de groupe de méthodes d’une méthode d’instance en type délégué.
  • En tant que variable capturée dans une expression lambda ou une fonction locale.

En outre, les restrictions suivantes s’appliquent à un ref struct type :

  • Un ref struct type ne doit pas être boxé à System.ValueType ou System.Object.
  • Un ref struct type ne doit pas être déclaré pour implémenter une interface.
  • Une méthode d’instance déclarée dans object ou in System.ValueType , mais non substituée dans un ref struct type, ne doit pas être appelée avec un récepteur de ce ref struct type.

Remarque : A ref struct ne doit pas déclarer async les méthodes d’instance ni utiliser une yield return instruction yield break dans une méthode d’instance, car le paramètre implicite this ne peut pas être utilisé dans ces contextes. Note de fin

Ces contraintes garantissent qu’une variable de type ne fait pas référence à la mémoire de ref struct pile qui n’est plus valide ou aux variables qui ne sont plus valides.

16.2.4 Modificateur partiel

Le partial modificateur indique que cette struct_declaration est une déclaration de type partielle. Plusieurs déclarations de struct partielles portant le même nom dans un espace de noms englobant ou une déclaration de type se combinent pour former une déclaration de struct, en suivant les règles spécifiées dans le §15.2.7.

Interfaces struct 16.2.5

Une déclaration de struct peut inclure une spécification struct_interfaces , auquel cas le struct est dit pour implémenter directement les types d’interface donnés. Pour un type de struct construit, y compris un type imbriqué déclaré dans une déclaration de type générique (§15.3.9.7), chaque type d’interface implémenté est obtenu en remplaçant, pour chaque type_parameter dans l’interface donnée, le type_argument correspondant du type construit.

struct_interfaces
    : ':' interface_type_list
    ;

La gestion des interfaces sur plusieurs parties d’une déclaration partielle de struct (§15.2.7) est abordée plus loin dans le §15.2.4.3.

Les implémentations d’interface sont abordées plus loin dans le §19.6.

16.2.6 Corps de struct

La struct_body d’un struct définit les membres du struct.

struct_body
    : '{' struct_member_declaration* '}'
    ;

16.3 Membres du struct

16.3.1 Général

Les membres d’un struct se composent des membres introduits par ses struct_member_declarations et les membres hérités du type System.ValueType. Pour un struct d’enregistrement, l’ensemble de membres inclut également les membres synthétisés générés par le compilateur (§synth-members).

struct_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | static_constructor_declaration
    | type_declaration
    | fixed_size_buffer_declaration   // unsafe code support
    ;

fixed_size_buffer_declaration (§24.8.2) n’est disponible que dans le code non sécurisé (§24).

Remarque : Toutes sortes de class_member_declarations, sauf finalizer_declaration sont également struct_member_declarations. Note de fin

À l’exception des différences indiquées dans le §16.5, les descriptions des membres de classe fournis dans le §15.3 et le §15.12 s’appliquent également aux membres du struct.

Il s’agit d’une erreur pour qu’un champ d’instance d’un struct d’enregistrement ait un type non sécurisé.

16.3.2 Membres readonly

Une définition ou un accesseur de membre d’instance d’une propriété, d’un indexeur ou d’un événement qui inclut le readonly modificateur a les restrictions suivantes :

  • Le this paramètre est une ref readonly référence.
  • Le membre ne réaffecte pas la valeur ou this un champ d’instance du récepteur.
  • Le membre ne réaffecte pas la valeur d’un événement de type champ d’instance (§15.8.2) du récepteur.
  • Si un membre en lecture seule appelle un membre non en lecture seule, la structure référencée par this doit être copiée pour utiliser une référence accessible en écriture pour l’argument this .

Note: Les champs d’instance incluent le champ de stockage masqué utilisé pour les propriétés implémentées automatiquement (§15.7.4). Note de fin

Exemple : un membre en lecture seule peut modifier l’état d’un objet référencé par un champ d’instance, même si le membre en lecture seule ne peut pas réaffecter ce membre d’instance. Le code suivant illustre la réaffectation et la modification d’un champ d’instance :

public struct S
{
    private List<string> messages;

    public S(IEnumerable<string> messages) =>
        this.messages = new List<string>(messages);

    public void InitializeMessages() =>
        messages = new List<string>();

    public readonly void AddMessage(string message)
    {
        if (messages == null)
        {
            throw new InvalidOperationException("Messages collection is not initialized.");
        }
        messages.Add(message);
    }
}

La readonly méthode AddMessage peut modifier l’état d’une liste de messages. Le InitializeMessages membre peut effacer et réinscrire la liste des messages. Dans le cas de AddMessage, le readonly modificateur est valide. Dans le cas de InitializeMessages, l’ajout du readonly modificateur n’est pas valide. exemple de fin

16.4 Membres du struct d’enregistrement synthétisé

16.4.1 Général

Dans le cas d’un struct d’enregistrement, les membres sont synthétisés, sauf si un membre avec une signature « correspondance » est déclaré dans le record_struct_body ou un membre concret non virtuel accessible avec une signature « correspondante » est hérité. Deux membres sont considérés comme correspondants s’ils ont la même signature ou seraient considérés comme « masquant » dans un scénario d’héritage. (Consultez signatures et surchargement §7.6.)

Les membres synthétisés sont décrits dans les sous-sections suivantes.

16.4.2 Membres d’égalité

Les membres d’égalité synthétisés sont similaires à ceux d’une classe d’enregistrement (§15.16.2), à l’exception de l’absence de EqualityContractvérifications null ou d’héritage.

Un struct R d’enregistrement implémente System.IEquatable<R> et inclut une surcharge fortement typée synthétisée de Equals(R other), qui est publique, comme suit :

public readonly bool Equals(R other);

Cette méthode peut être déclarée explicitement. Toutefois, il s’agit d’une erreur si la déclaration explicite ne correspond pas à la signature ou à l’accessibilité attendue.

S’il Equals(R other) s’agit d’un avertissement défini par l’utilisateur (autrement dit, non synthétisé), mais GetHashCode non, un avertissement doit être généré.

La synthèse Equals(R) doit retourner true si et seulement si pour chaque champ fieldN d’instance dans le struct d’enregistrement la valeur de System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN), où TN est le type de champ, est true.

Le struct d’enregistrement comprend des opérateurs synthétisés et != é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 Equals méthode appelée par l’opérateur == est la Equals(R other) méthode 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.

Le struct d’enregistrement inclut un remplacement synthétisé é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é retourne other is R temp && Equals(temp)R où se trouve le struct d’enregistrement.

Le struct d’enregistrement inclut un remplacement synthétisé équivalent à une méthode déclarée comme suit :

public override readonly int GetHashCode();

Cette méthode peut être déclarée explicitement.

Un avertissement doit être signalé si l’un d’eux Equals(R) et GetHashCode() est explicitement déclaré, mais que l’autre méthode n’est pas.

Le remplacement synthétisé de GetHashCode() doit retourner un int résultat de la combinaison des valeurs de System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) chaque champ fieldN d’instance avec TN le type de fieldN.

Exemple : Considérez le struct d’enregistrement suivant :

record struct R1(T1 P1, T2 P2);

Pour cela, 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 HashCode.Combine(
            EqualityComparer<T1>.Default.GetHashCode(P1),
            EqualityComparer<T2>.Default.GetHashCode(P2));

exemple de fin

16.4.3 Membres d’impression

Un struct d’enregistrement inclut une méthode synthétisée équivalente à ce qui suit :

private bool PrintMembers(System.Text.StringBuilder builder);

Cette méthode effectue les tâches suivantes :

  1. Pour chacun des membres imprimables du struct d’enregistrement (champ public non statique et membres de propriété lisible), ajoute le nom de ce membre suivi de «= » suivi de la valeur du membre séparé par «, “ ,
  2. Retourne true si le struct d’enregistrement a des membres imprimables.

Pour un membre qui a un type valeur, sa valeur doit être convertie en représentation sous forme de chaîne.

Si les membres imprimables de l’enregistrement n’incluent pas de propriété lisible avec un non-accesseurreadonlyget , la synthèse PrintMembers est readonly. Il n’est pas nécessaire que les champs de l’enregistrement soient readonly pour que la PrintMembers méthode soit readonly.

La PrintMembers méthode peut être déclarée explicitement. Toutefois, il s’agit d’une erreur si la déclaration explicite ne correspond pas à la signature ou à l’accessibilité attendue.

Le struct d’enregistrement comprend une méthode synthétisée équivalente à ce qui suit :

public override string ToString();

Si la méthode du struct d’enregistrement PrintMembers est readonly, la méthode synthétisée ToString() doit être readonly.

Cette 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.

Cette méthode effectue les tâches suivantes :

  1. Crée une StringBuilder instance,
  2. Ajoute le nom du struct d’enregistrement au générateur, suivi de «{ »,
  3. Appelle la méthode du PrintMembers struct d’enregistrement qui lui donne le générateur, suivi de « » s’il a retourné true,
  4. Ajoute « »,}
  5. Retourne le contenu du générateur avec builder.ToString().

Exemple : Considérez le struct d’enregistrement suivant :

record struct R1(T1 P1, T2 P2);

Pour ce struct d’enregistrement, les membres d’impression synthétisés seraient similaires à :

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();
    }
}

exemple de fin

16.4.4 Membres du struct d’enregistrement positionnel

16.4.4.1 Général

En plus de fournir les membres décrits dans les sous-sections précédentes, les structs d’enregistrement positionnel (§16.2.1) synthétisent des membres supplémentaires avec les mêmes conditions que les autres membres, comme décrit dans les sous-sections suivantes.

16.4.4.2 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 pour le type. Il s’agit d’une erreur d’avoir un constructeur principal et un constructeur avec la même signature déjà présente dans le struct. Si la déclaration de type n’inclut pas de delimited_parameter_list, aucun constructeur principal n’est généré.

record struct R1
{
    public R1() { } // OK
}

record struct R2()
{
    public R2() { } // error: 'R2' already defines
                    // a constructor with the same parameter types
}

Les déclarations de champ d’instance d’un struct d’enregistrement 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 principal exécute les initialiseurs d’instance qui apparaissent dans le corps du struct-enregistrement.

Si un struct d’enregistrement a un constructeur principal, tout constructeur défini par l’utilisateur doit avoir un initialiseur de constructeur explicite this qui appelle le constructeur principal ou un constructeur déclaré explicitement.

Les paramètres du constructeur principal ainsi que les membres du struct d’enregistrement sont dans l’étendue dans les initialiseurs des champs d’instance ou des propriétés. Les membres d’instance seraient une erreur dans ces emplacements, mais les paramètres du constructeur principal seraient dans l’étendue et utilisables et seraient des membres d’ombre. Les membres statiques peuvent également être utilisés.

Un avertissement doit être 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 struct s’appliquent au constructeur principal des structs d’enregistrement. Par exemple, voici une erreur :

record struct Pos(int X) // def assignment error in primary constructor
{
    private int x;
    public int X {
        get { return x; } set { x = value; } 
    } = X;
}

16.4.4.3 Propriétés

Pour chaque paramètre d’un delimited_parameter_list qui a le même nom et le même type qu’un champ d’instance déclaré explicitement, le reste de ce sous-volet ne s’applique pas.

Pour chaque paramètre de struct d’enregistrement d’un delimited_parameter_list il existe un membre de propriété publique correspondant dont le nom et le type sont extraits de la déclaration de paramètre valeur.

Pour un struct d’enregistrement :

  • une propriété publique get et init automatique est créée si le struct d’enregistrement a un readonly modificateur, get et set sinon. Les deux types d’accesseurs set (set et init) sont considérés comme « correspondants ». Par conséquent, l’utilisateur peut déclarer une propriété init uniquement à la place d’un mutable synthétisé.

  • Une propriété héritée abstract 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 pas et/setinit n’a publicget pas d’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é automatique synthétisée et à son champ de stockage à l’aide property: ou field: aux cibles des attributs appliqués de manière syntactique au paramètre de struct d’enregistrement correspondant.

16.4.4.4 Déconstruct

Un struct d’enregistrement positionnel avec au moins un paramètre synthétise une méthode d’instance publique void-retournant appelée Deconstruct avec une déclaration de paramètre out pour chaque paramètre de la déclaration du constructeur principal. Chaque paramètre a Deconstruct 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 qui accède à un membre du même nom. Si les membres d’instance accessibles dans le corps n’incluent pas de propriété avec un non-accesseurreadonlyget , la méthode synthétisée Deconstruct 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.

Différences de classe et de struct 16.5

16.5.1 Général

Les structs diffèrent des classes de plusieurs façons importantes :

  • Les structs sont des types valeur (§16.5.2).
  • Tous les types de struct héritent implicitement de la classe System.ValueType (§16.5.3).
  • L’affectation à une variable d’un type de struct crée une copie de la valeur affectée (§16.5.4).
  • La valeur par défaut d’un struct est la valeur produite en définissant tous les champs sur leur valeur par défaut (§16.5.5).
  • Les opérations boxing et unboxing sont utilisées pour convertir entre un type de struct et certains types de référence (§16.5.6).
  • La signification est this différente dans les membres du struct (§16.5.7).
  • Un struct n’est pas autorisé à déclarer un finaliseur.
  • Les déclarations d’événements, de propriétés, d'accesseurs de propriété, d’indexeurs et de méthodes peuvent avoir le modificateur readonly, bien que cela ne soit généralement pas permis pour ces mêmes types de membres dans les classes.

Sémantique des valeurs 16.5.2

Les structs sont des types valeur (§8.3) et sont dits avoir une sémantique de valeur. Les classes, d’autre part, sont des types de référence (§8.2) et sont dits avoir une sémantique de référence.

Une variable d’un type de struct contient directement les données du struct, tandis qu’une variable d’un type de classe contient une référence à un objet qui contient les données. Lorsqu’un struct B contient un champ d’instance de type A et A qu’il s’agit d’un type de struct, il s’agit d’une erreur au moment de la compilation pour A dépendre B ou d’un type construit à partir de B. Un struct Xdépend directement de d’un struct Y si X contient un champ d’instance de type Y. Étant donné cette définition, l’ensemble complet de structs sur lesquels dépend un struct est la fermeture transitive de la relation directe .

Exemple :

struct Node
{
    int data;
    Node next; // error, Node directly depends on itself
}

est une erreur, car Node contient un champ d’instance de son propre type. Autre exemple :

struct A { B b; }
struct B { C c; }
struct C { A a; }

est une erreur, car chacun des types A, Bet C dépend les uns des autres.

exemple de fin

Avec les classes, il est possible que deux variables référencent le même objet, et ainsi que les opérations sur une variable affectent l’objet référencé par l’autre variable. Avec les structs, les variables ont chacune leur propre copie des données (sauf dans le cas de paramètres de référence) et il n’est pas possible d’effectuer des opérations sur l’une d’elles pour affecter l’autre. En outre, sauf lorsqu’il est explicitement nullable (§8.3.12), il n’est pas possible que les valeurs d’un type de struct soient null.

Remarque : si un struct contient un champ de type référence, le contenu de l’objet référencé peut être modifié par d’autres opérations. Toutefois, la valeur du champ lui-même, c’est-à-dire l’objet auquel il fait référence, ne peut pas être modifiée par une mutation d’une valeur de struct différente. Note de fin

Exemple : compte tenu des éléments suivants

struct Point
{
    public int x, y;

    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }
}

class A
{
    static void Main()
    {
        Point a = new Point(10, 10);
        Point b = a;
        a.x = 100;
        Console.WriteLine(b.x);
    }
}

la sortie est 10. Affectation de a créer b une copie de la valeur et b n’est donc pas affectée par l’affectation à a.x. Au Point lieu de cela, la sortie aurait 100 été déclarée en tant que classe, car a et b référencerait le même objet.

exemple de fin

Héritage 16.5.3

Tous les types de struct héritent implicitement de la classe System.ValueType, qui hérite à son tour de la classe object. Une déclaration de struct peut spécifier une liste d’interfaces implémentées, mais il n’est pas possible qu’une déclaration de struct spécifie une classe de base.

Les types de structs ne sont jamais abstraits et sont toujours implicitement scellés. Les abstract modificateurs et sealed les modificateurs ne sont donc pas autorisés dans une déclaration de struct.

Étant donné que l’héritage n’est pas pris en charge pour les structs, l’accessibilité déclarée d’un membre de struct ne peut pas être protected, private protectedou protected internal.

Les membres de fonction d’un struct ne peuvent pas être abstraits ou virtuels, et le override modificateur est autorisé uniquement à remplacer les méthodes héritées de System.ValueType.

16.5.4 Affectation

L’affectation à une variable d’un type de struct crée une copie de la valeur affectée. Cela diffère de l’affectation à une variable d’un type de classe, qui copie la référence, mais pas l’objet identifié par la référence.

Comme pour une affectation, lorsqu’un struct est passé en tant que paramètre de valeur ou retourné à la suite d’un membre de fonction, une copie du struct est créée. Un struct peut être passé par référence à un membre de fonction à l’aide d’un paramètre de référence.

Lorsqu’une propriété ou un indexeur d’un struct est la cible d’une affectation, l’expression d’instance associée à l’accès à la propriété ou à l’indexeur doit être classifiée comme variable. Si l’expression d’instance est classifiée comme valeur, une erreur au moment de la compilation se produit. Ceci est décrit plus en détail dans le §12.24.2.

16.5.5 Valeurs par défaut

Comme décrit dans le §9.3, plusieurs types de variables sont automatiquement initialisés à leur valeur par défaut lorsqu’elles sont créées. Pour les variables des types de classes et d’autres types de référence, cette valeur par défaut est null. Toutefois, étant donné que les structs sont des types valeur qui ne peuvent pas être null, la valeur par défaut d’un struct est la valeur produite en définissant tous les champs de type valeur sur leur valeur par défaut et tous les champs de type référence sur null.

Exemple : référence au Point struct déclaré ci-dessus, l’exemple

Point[] a = new Point[100];

initialise chacun Point dans le tableau à la valeur produite en définissant les champs et x les y champs sur zéro.

exemple de fin

La valeur par défaut d’un struct correspond à la valeur retournée par le constructeur par défaut du struct (§8.3.3). Lorsqu’un struct ne déclare pas de constructeur d’instance sans paramètre explicite, le constructeur par défaut est synthétisé et retourne toujours la valeur qui résulte de la définition de tous les champs à leurs valeurs par défaut. L’expression default produit toujours la valeur par défaut initialisée zéro, même lorsqu’un struct déclare un constructeur d’instance sans paramètre explicite (§16.4.9).

Remarque : les structs doivent être conçus pour prendre en compte l’état d’initialisation par défaut un état valide. Dans l’exemple

struct KeyValuePair
{
    string key;
    string value;

    public KeyValuePair(string key, string value)
    {
        if (key == null || value == null)
        {
            throw new ArgumentException();
        }

        this.key = key;
        this.value = value;
    }
}

le constructeur d’instance défini par l’utilisateur protège contre null les valeurs uniquement lorsqu’il est explicitement appelé. Dans les cas où une variable est soumise à l’initialisation KeyValuePair de valeur par défaut, les champs et key les value champs seront null, et le struct doit être prêt à gérer cet état.

Note de fin

16.5.6 Boxing et unboxing

Une valeur d’un type de classe peut être convertie en type object ou en type d’interface implémentée par la classe simplement en traitant la référence comme un autre type au moment de la compilation. De même, une valeur de type object ou une valeur d’un type d’interface peut être convertie en type de classe sans modifier la référence (mais bien sûr, une vérification de type d’exécution est requise dans ce cas).

Étant donné que les structs ne sont pas des types de référence, ces opérations sont implémentées différemment pour les types de struct. Lorsqu’une valeur d’un type de struct est convertie en certains types de référence (comme défini dans le §10.2.9), une opération de boxe a lieu. De même, lorsqu’une valeur de certains types de référence (telle que définie dans le §10.3.7) est convertie en type de struct, une opération d’annulation de boîte de réception a lieu. Une différence clé par rapport aux mêmes opérations sur les types de classes est que boxing et unboxing copie la valeur de struct dans ou hors de l’instance boxed.

Remarque : Par conséquent, en suivant une opération de boxe ou de déséboxage, les modifications apportées à l’unboxed struct ne sont pas reflétées dans le boxed struct. Note de fin

Pour plus d’informations sur la boxe et le déboxing, consultez §10.2.9 et §10.3.7.

16.5.7 Signification de ce

La signification d’un this struct diffère de la signification d’une this classe, comme décrit dans le §12.8.14. Lorsqu’un type de struct substitue une méthode virtuelle héritée de System.ValueType (par Equalsexemple, , GetHashCodeou ToString), l’appel de la méthode virtuelle via une instance du type de struct n’entraîne pas de boxing. Cela est vrai même lorsque le struct est utilisé comme paramètre de type et que l’appel se produit par le biais d’une instance du type de paramètre de type.

Exemple :

struct Counter
{
    int value;
    public override string ToString() 
    {
        value++;
        return value.ToString();
    }
}

class Program
{
    static void Test<T>() where T : new()
    {
        T x = new T();
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
        Console.WriteLine(x.ToString());
    }

    static void Main() => Test<Counter>();
}

La sortie du programme est la suivante :

1
2
3

Bien qu’il soit mauvais pour ToString avoir des effets secondaires, l’exemple montre qu’aucune boxe n’a eu lieu pour les trois appels de x.ToString().

exemple de fin

De même, la boxe ne se produit jamais implicitement lors de l’accès à un membre sur un paramètre de type contraint lorsque le membre est implémenté dans le type valeur. Par exemple, supposons qu’une interface ICounter contient une méthode Increment, qui peut être utilisée pour modifier une valeur. Si ICounter elle est utilisée comme contrainte, l’implémentation de la Increment méthode est appelée avec une référence à la variable appelée Increment , jamais une copie boxée.

Exemple :

interface ICounter
{
    void Increment();
}

struct Counter : ICounter
{
    int value;

    public override string ToString() => value.ToString();

    void ICounter.Increment() => value++;
}

class Program
{
    static void Test<T>() where T : ICounter, new()
    {
        T x = new T();
        Console.WriteLine(x);
        x.Increment();              // Modify x
        Console.WriteLine(x);
        ((ICounter)x).Increment();  // Modify boxed copy of x
        Console.WriteLine(x);
    }

    static void Main() => Test<Counter>();
}

Le premier appel pour Increment modifier la valeur dans la variable x. Cela n’équivaut pas au deuxième appel à Increment, qui modifie la valeur dans une copie boxée de x. Ainsi, la sortie du programme est la suivante :

0
1
1

exemple de fin

16.5.8 Initialiseurs de champ

Comme décrit dans le §16.5.5, la valeur par défaut d’un struct se compose de la valeur qui résulte de la définition de tous les champs de type valeur sur leur valeur par défaut et de tous les champs de type référence sur null. Les champs statiques et d’instance d’un struct sont autorisés à inclure des initialiseurs de variables ; Toutefois, dans le cas d’un initialiseur de champ d’instance, au moins un constructeur d’instance doit également être déclaré, ou pour un struct d’enregistrement, un delimited_parameter_list doit être présent.

Exemple :

Console.WriteLine($"Point is {new Point()}");

struct Point
{
    public int x = 1;
    public int y = 1;

    public Point() { }

    public override string ToString()
    {
        return "(" + x + ", " + y + ")";
    }
}
Point is (1, 1)

exemple de fin

Lorsqu’un constructeur d’instance de struct n’a pas d’initialiseur de constructeur, ce constructeur effectue implicitement les initialisations spécifiées par les variable_initializerdes champs d’instance déclarés dans son struct. Cela correspond à une séquence d’affectations exécutées immédiatement lors de l’entrée au constructeur.

Lorsqu’un constructeur d’instance de struct a un this() initialiseur de constructeur qui représente le constructeur sans paramètre par défaut, le constructeur déclaré efface implicitement tous les champs d’instance et effectue les initialisations spécifiées par les variable_initializerdes champs d’instance déclarés dans son struct. Immédiatement lors de l’entrée au constructeur, tous les champs de type valeur sont définis sur leur valeur par défaut et tous les champs de type référence sont définis sur null. Immédiatement après cela, une séquence d’affectations correspondant aux variable_initializers est exécutée.

Une field_declaration déclarée directement à l’intérieur d’une struct_declaration ayant le struct_modifierreadonly doit avoir le field_modifierreadonly.

16.5.9 Constructeurs

Un struct peut déclarer des constructeurs d’instance, avec zéro ou plusieurs paramètres. Si un struct n’a aucun constructeur d’instance sans paramètre explicitement déclaré, il est synthétisé, avec accessibilité publique, qui retourne toujours la valeur qui résulte de la définition de tous les champs de type valeur à leur valeur par défaut et de tous les champs de type référence sur null (§8.3.3). Dans ce cas, tous les initialiseurs de champ d’instance sont ignorés lorsque ce constructeur s’exécute.

Un constructeur d’instance sans paramètre déclaré explicitement doit avoir une accessibilité publique.

Exemple : Étant donné les éléments suivants :

using System;
struct Point
{
    int x = -1, y = -2;

    public Point(int x, int y) 
    {
        this.x = x;
        this.y = y;
    }

    public override string ToString()
    {
        return "(" + x + ", " + y + ")";
    }
}

class A
{
    static void Main()
    {
        Console.WriteLine($"Point is {new Point()}");
        Console.WriteLine($"Point is {new Point(0,0)}");
    }
}
Point is (0, 0)
Point is (0, 0)

les instructions créent une Point instruction avec x et y initialisée à zéro, qui, dans le cas de l’appel au constructeur d’instance sans paramètre, peut être surprenant, car les deux champs d’instance ont des initialiseurs, mais ils ne sont pas exécutés.

exemple de fin

Un constructeur d’instance de struct n’est pas autorisé à inclure un initialiseur de constructeur du formulaire base(argument_list), où argument_list est facultatif. L’exécution d’un constructeur d’instance ne génère pas l’exécution d’un constructeur dans le type System.ValueTypede base du struct.

Le this paramètre d’un constructeur d’instance de struct correspond à un paramètre de sortie du type de struct. Par conséquent, this doit être définitivement affecté (§9.4) à chaque emplacement où le constructeur retourne. De même, il ne peut pas être lu (même implicitement) dans le corps du constructeur avant d’être définitivement affecté.

Si le constructeur d’instance de struct spécifie un initialiseur de constructeur, cet initialiseur est considéré comme une affectation définitive à ceci qui se produit avant le corps du constructeur. Par conséquent, le corps lui-même n’a pas d’exigences d’initialisation.

Les champs d’instance (autres que fixed les champs) doivent être affectés définitivement dans les constructeurs d’instance de struct qui n’ont pas d’initialiseur this() .

Exemple : Considérez l’implémentation du constructeur d’instance ci-dessous :

struct Point
{
    int x, y;

    public int X
    {
        set { x = value; }
    }

    public int Y 
    {
        set { y = value; }
    }

    public Point(int x, int y) 
    {
        X = x; // error, this is not yet definitely assigned
        Y = y; // error, this is not yet definitely assigned
    }
}

Aucun membre de fonction d’instance (y compris les accesseurs set pour les propriétés X et Y) ne peut être appelé tant que tous les champs du struct en cours de construction n’ont pas été définitivement affectés. Notez toutefois que, s’il Point s’agissait d’une classe au lieu d’un struct, l’implémentation du constructeur d’instance serait autorisée. Il existe une exception à cela, et cela implique des propriétés implémentées automatiquement (§15.7.4). Les règles d’affectation définies (§12.24.2) exemptent spécifiquement l’affectation à une propriété automatique d’un type de struct au sein d’un constructeur d’instance de ce type de struct : une telle affectation est considérée comme une affectation définitive du champ de stockage masqué de la propriété automatique. Ainsi, les éléments suivants sont autorisés :

struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y)
    {
        X = x; // allowed, definitely assigns backing field
        Y = y; // allowed, definitely assigns backing field
   }
}

exemple de fin]

16.5.10 Constructeurs statiques

Les constructeurs statiques pour les structs suivent la plupart des mêmes règles que pour les classes. L’exécution d’un constructeur statique pour un type de struct est déclenchée par le premier des événements suivants à exécuter dans un domaine d’application :

  • Un membre statique du type de struct est référencé.
  • Un constructeur déclaré explicitement du type de struct est appelé.

Remarque : La création de valeurs par défaut (§16.5.5) de types de structs ne déclenche pas le constructeur statique. (Par exemple, il s’agit de la valeur initiale des éléments d’un tableau.) Note de fin

16.5.11 Propriétés

Un property_declaration (§15.7.1) pour une propriété d’instance dans un struct_declaration peut contenir le property_modifierreadonly. Toutefois, une propriété statique ne doit pas contenir ce modificateur.

Il s’agit d’une erreur de compilation que de tenter de modifier l’état d’une variable de structure d’instance via une propriété en seule lecture déclarée dans cette structure.

Il s'agit d'une erreur de compilation qu'une propriété implémentée automatiquement ayant un modificateur readonly ait également un accesseur set.

Il s'agit d'une erreur de compilation lorsqu'une propriété est implémentée automatiquement dans une struct readonly et a un accesseur set.

Une propriété implémentée automatiquement déclarée à l’intérieur d’un readonly struct n’a pas besoin d’un readonly modificateur, car son get accesseur est implicitement supposé être en lecture seule.

Il s'agit d'une erreur de compilation d'avoir un readonly modificateur sur une propriété elle-même ainsi que sur ses accesseurs get et set.

Il s’agit d’une erreur à la compilation pour une propriété ayant un modificateur de lecture seule appliqué à tous ses accesseurs.

Remarque : Pour corriger l’erreur, déplacez le modificateur des accesseurs vers la propriété elle-même. Note de fin

Pour une expression d’accesseur de propriété, s.P:

  • Il s’agit d’une erreur au moment de la compilation si s.P l’appel de l’accesseur M de jeu de type T lorsque le processus du §12.6.6.1 créerait une copie temporaire de s.
  • Si s.P l’appelant appelle l’accesseur get de type T, le processus du §12.6.6.1 est suivi, y compris la création d’une copie temporaire du s cas échéant.

Les propriétés implémentées automatiquement (§15.7.4) utilisent des champs de stockage masqués, qui sont accessibles uniquement aux accesseurs de propriétés.

Remarque : Cette restriction d’accès signifie que les constructeurs dans les structs contenant des propriétés implémentées automatiquement ont souvent besoin d’un initialiseur de constructeur explicite où ils n’en auraient pas besoin autrement, pour satisfaire à l’exigence de tous les champs affectés définitivement avant qu’un membre de fonction soit appelé ou que le constructeur retourne. Note de fin

16.5.12 Méthodes

Une method_declaration (§15.6.1) pour une méthode d’instance dans un struct_declaration peut contenir le method_modifierreadonly. Toutefois, une méthode statique ne doit pas contenir ce modificateur.

Il s’agit d’une erreur au moment de la compilation pour tenter de modifier l’état d’une variable de struct d’instance via une méthode en lecture seule déclarée dans ce struct.

Bien qu’une méthode readonly puisse appeler une méthode parallèle, non readonly, une propriété ou un accesseur get d'indexeur, cela entraîne la création d’une copie implicite de this comme mesure défensive.

Une méthode en lecture seule peut appeler une propriété sœur ou un accesseur d'indexeur en lecture seule. Si l’accesseur d’un membre frère n’est pas explicitement ou implicitement en lecture seule, une erreur de compilation se produit.

Toutes les method_declaration d'une méthode partielle doivent avoir un readonly modificateur, ou aucune ne doit en avoir.

16.5.13 Indexeurs

Un indexer_declaration (§15.9) pour un indexeur d’instance dans un struct_declaration peut contenir le indexer_modifierreadonly.

Il s’agit d’une erreur à la compilation de tenter de modifier l’état d'une variable struct d’une instance via un indexeur en lecture seule déclaré dans cette structure.

Il s'agit d'une erreur de compilation d'avoir un modificateur readonly sur l'indexeur lui-même ainsi que sur ses accesseurs get ou set.

Il s’agit d’une erreur de compilation qu'un indexeur ait un modificateur readonly sur tous ses accesseurs.

Remarque : Pour corriger l’erreur, déplacez le modificateur des accesseurs vers l’indexeur lui-même. Note de fin

16.5.14 Événements

Un event_declaration (§15.8.1) pour un événement de type non champ dans un struct_declaration peut contenir la event_modifierreadonly. Toutefois, un événement statique ne doit pas contenir ce modificateur.

Contrainte de contexte sécurisé 16.5.15

16.5.15.1 Général

Au moment de la compilation, chaque expression est associée à un contexte dans lequel cette instance et tous ses champs sont accessibles en toute sécurité, son contexte sécurisé. Le contexte sécurisé est un contexte englobant une expression dans laquelle il est sûr que la valeur s’échappe.

Toute expression dont le type au moment de la compilation n’est pas un struct ref a un contexte sécurisé d’appelant-contexte.

Une default expression, pour n’importe quel type, a un contexte sécurisé d’appelant-contexte.

Pour toute expression non par défaut dont le type au moment de la compilation est un struct ref a un contexte sécurisé défini par les sections suivantes.

Enregistrements de contexte sécurisé dans lesquels une valeur peut être copiée. Étant donné une affectation d’une expression E1 avec un contexte S1sécurisé , à une expression E2 avec un contexte S2sécurisé , il s’agit d’une erreur si S2 c’est un contexte plus large que S1.

Il existe trois valeurs de contexte sécurisé différentes, identiques aux valeurs de contexte ref-safe définies pour les variables de référence (§9.7.2) : le bloc de déclaration, le membre de fonction et le contexte de l’appelant. Le contexte sécurisé d’une expression limite son utilisation comme suit :

  • Pour une instruction return e1de retour, le contexte sécurisé de e1 doit être caller-context.
  • Pour une affectation e1 = e2 , le contexte sûr de e2 doit être au moins aussi large qu’un contexte sûr de e1.

Pour un appel de méthode s’il existe un ref ou out un argument d’un ref struct type (y compris le récepteur, sauf si le type est readonly), avec un contexte S1sécurisé , aucun argument (y compris le récepteur) peut avoir un contexte sécurisé plus étroit que S1.

16.5.15.2 Contexte sécurisé du paramètre

Un paramètre d’un type de struct ref, y compris le this paramètre d’une méthode d’instance, a un contexte sécurisé d’appelant-contexte.

16.5.15.3 Contexte sécurisé de variable locale

Une variable locale d’un type de struct ref a un contexte sécurisé comme suit :

  • Si la variable est une variable d’itération d’une foreach boucle, le contexte sécurisé de la variable est identique au contexte sécurisé de l’expression de la foreach boucle.
  • Sinon, si la déclaration de la variable a un initialiseur, le contexte sécurisé de la variable est identique au contexte sécurisé de cet initialiseur.
  • Sinon, la variable n’est pas initialisée au point de déclaration et a un contexte sécurisé d’appelant-contexte.

16.5.15.4 Contexte sécurisé du champ

Une référence à un champ e.F, où le type de F type est un type de struct ref, a un contexte sécurisé qui est identique au contexte sécurisé de e.

16.5.15.5 Opérateurs

L’application d’un opérateur défini par l’utilisateur est traitée comme un appel de méthode (§16.5.15.6).

Pour un opérateur qui génère une valeur, par exemple e1 + e2 , c ? e1 : e2le contexte sécurisé du résultat est le contexte le plus étroit parmi les contextes sécurisés des opérandes de l’opérateur. Par conséquent, pour un opérateur unaire qui génère une valeur, par +eexemple, le contexte sécurisé du résultat est le contexte sécurisé de l’opérande.

Remarque : Le premier opérande d’un opérateur conditionnel est un bool, de sorte que son contexte sécurisé est le contexte appelant. Il suit que le contexte sécurisé résultant est le contexte sécurisé le plus étroit du deuxième et du troisième opérande. Note de fin

16.5.15.6 Méthode et appel de propriété

Une valeur résultant d’un appel de méthode ou d’un appel e1.M(e2, ...)e.P de propriété a un contexte sécurisé du plus petit des contextes suivants :

  • contexte de l'appelant
  • Contexte sécurisé de toutes les expressions d’argument (y compris le récepteur).

Un appel de propriété (ou getset) est traité comme un appel de méthode de la méthode sous-jacente par les règles ci-dessus.

16.5.15.7 stackalloc

Le résultat d’une expression stackalloc a un contexte sécurisé de membre de fonction.

16.5.15.8 Appels de constructeur

Expression new qui appelle un constructeur obéit aux mêmes règles qu’un appel de méthode qui est considéré comme renvoyant le type construit.

En outre, le contexte sécurisé est le plus petit des contextes sécurisés de tous les arguments et opérandes de toutes les expressions d’initialiseur d’objet, de manière récursive, si un initialiseur est présent.

Remarque : Ces règles s’appuient sur Span<T> l’échec d’un constructeur du formulaire suivant :

public Span<T>(ref T p)

Un tel constructeur rend les instances utilisées Span<T> en tant que champs indistinguishables à partir d’un ref champ. Les règles de sécurité décrites dans ce document dépendent de ref champs qui ne sont pas une construction valide en C# ou .NET. Note de fin