Constructeurs de structure sans paramètre
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/99
Résumé
Prendre en charge les constructeurs sans paramètre et les initialiseurs de champs d’instance pour les types struct.
Motivation
Les constructeurs sans paramètre explicites donnent davantage de contrôle sur les instances minimalement construites du type de struct.
Les initialiseurs de champs d’instance autorisent l’initialisation simplifiée entre plusieurs constructeurs.
Ensemble, ceux-ci combleraient un écart évident entre les déclarations struct
et class
.
La prise en charge des initialiseurs de champs autorise également l’initialisation de champs dans record struct
déclarations sans implémenter explicitement le constructeur principal.
record struct Person(string Name)
{
public object Id { get; init; } = GetNextId();
}
Si les initialiseurs de champs de struct sont pris en charge pour les constructeurs avec des paramètres, il semble naturel d’étendre également la prise en charge pour les constructeurs sans paramètre.
record struct Person()
{
public string Name { get; init; }
public object Id { get; init; } = GetNextId();
}
Proposition
Initialiseurs de champs d’instance
Les déclarations de champ d’instance pour un struct peuvent inclure des initialiseurs.
Comme avec les initialiseurs de champ de classe §15.5.6.3:
Un initialiseur de variable pour un champ d’instance ne peut pas référencer l’instance en cours de création.
Une erreur est signalée si un struct comporte des initialiseurs de champs et qu’aucun constructeur d’instance déclaré n’est déclaré, car les initialiseurs de champ ne seront pas exécutés.
struct S { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor
Constructeurs
Un struct peut déclarer un constructeur d’instance sans paramètre.
Un constructeur d’instance sans paramètre est valide pour tous les types de structs, notamment struct
, readonly struct
, ref struct
et record struct
.
Si aucun constructeur d’instance sans paramètre n’est déclaré, le struct (voir §16.4.9) ...
a implicitement un constructeur d’instance sans paramètre 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.
Modificateurs
Un constructeur de struct d’instance sans paramètre doit être déclaré public
.
struct S0 { } // ok
struct S1 { public S1() { } } // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'
Les constructeurs non publics sont ignorés lors de l’importation de types à partir de métadonnées.
Les constructeurs peuvent être déclarés extern
ou unsafe
.
Les constructeurs ne peuvent pas être partial
.
Exécution d’initialiseurs de champs
Initialiseurs de variables d’instance (§15.11.3) est modifié comme suit :
Lorsqu’un constructeur d’instance de classe n’a pas d’initialiseur de constructeur ou qu’il a un initialiseur de constructeur de la forme
base(...)
, ce constructeur effectue implicitement les initialisations spécifiées par les initialiseurs variable_initializer des champs d’instance déclarés dans sa classe. Cela correspond à une séquence d’affectations exécutées immédiatement lors de l’entrée dans le constructeur et avant l’appel implicite du constructeur de classe de base directe.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 initialiseurs variable_initializer des 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 dans le constructeur.
Lorsqu’un constructeur d’instance de structure a un initialiseur de constructeur
this()
qui représente le constructeur sans paramètre par défaut, le constructeur déclaré efface implicitement tous les champs d’instance et exécute les initialisations spécifiées par les initialiseurs variable_initializer des 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 surnull
. Immédiatement après cela, une séquence d’affectations correspondant aux initialiseurs de variables est exécutée.
Affectation définitive
Les champs d’instance (autres que les champs fixed
) doivent être affectés définitivement dans les constructeurs d’instances de struct qui n’ont pas d’initialiseur this()
(voir §16.4.9).
struct S0 // ok
{
int x;
object y;
}
struct S1 // error: 'struct' with field initializers must include an explicitly declared constructor
{
int x = 1;
object y;
}
struct S2
{
int x = 1;
object y;
public S2() { } // error in C# 10 (valid starting in C# 11): field 'y' must be assigned
}
struct S3 // ok
{
int x = 1;
object y;
public S3() { y = 2; }
}
Aucun initialiseur base()
Un initialiseur base()
est interdit dans les constructeurs de structures.
Le compilateur n’émet pas d’appel au constructeur System.ValueType
de base à partir des constructeurs d’instance de struct.
record struct
Une erreur est signalée si un record struct
a des initialiseurs de champs et ne contient pas de constructeur principal ni de constructeur d’instance, car les initialiseurs de champ ne seront pas exécutés.
record struct R0; // ok
record struct R1 { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor
record struct R2() { int F = 42; } // ok
record struct R3(int F); // ok
Une record struct
avec une liste de paramètres vide aura un constructeur principal sans paramètre.
record struct R3(); // primary .ctor: public R3() { }
record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; }
Un constructeur explicite sans paramètre dans un record struct
doit avoir un initialiseur this
qui appelle le constructeur principal ou un constructeur déclaré explicitement.
record struct R5(int F)
{
public R5() { } // error: must have 'this' initializer that calls explicit .ctor
public R5(object o) : this() { } // ok
public int F = F;
}
Champs
Le constructeur sans paramètre défini implicitement initialisera les champs à zéro au lieu d'appeler des constructeurs sans paramètre pour les types de champs. Aucun avertissement n'indique que les constructeurs de champs sont ignorés. Aucune modification de C#9.
struct S0
{
public S0() { }
}
struct S1
{
S0 F; // S0 constructor ignored
}
struct S<T> where T : struct
{
T F; // constructor (if any) ignored
}
default
expression
default
ignore le constructeur sans paramètre et génère une instance nulle.
Aucune modification de C#9.
// struct S { public S() { } }
_ = default(S); // constructor ignored, no warning
new()
La création d’objets appelle le constructeur sans paramètre si public ; sinon, l’instance est zéro. Aucune modification de C#9.
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
_ = new PublicConstructor(); // call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // initobj PrivateConstructor
Une vague d’avertissement peut signaler un avertissement pour l’utilisation de new()
avec un type struct qui a des constructeurs, mais aucun constructeur par défaut.
Aucun avertissement ne sera signalé lors de la substitution d'un type struct pour un paramètre de type avec une contrainte new()
ou struct
.
struct S { public S(int i) { } }
static T CreateNew<T>() where T : new() => new T();
_ = new S(); // no warning called
_ = CreateNew<S>(); // ok
Valeurs non initialisées
Un champ local ou d’un type de struct qui n’est pas initialisé explicitement est zéro. Le compilateur signale une erreur d’affectation définie pour un struct non initialisé qui n’est pas vide. Aucune modification de C#9.
NoConstructor s1;
PublicConstructor s2;
s1.ToString(); // error: use of unassigned local (unless type is empty)
s2.ToString(); // error: use of unassigned local (unless type is empty)
Allocation de tableau
L’allocation de tableau ignore tous les constructeurs sans paramètre et génère des éléments initialisés à zéro. Aucune modification de C#9.
// struct S { public S() { } }
var a = new S[1]; // constructor ignored, no warning
Valeur par défaut du paramètre new()
Une valeur par défaut de paramètre de new()
est liée au constructeur sans paramètre si elle est publique (et signale une erreur indiquant que la valeur n’est pas constante) ; sinon, l’instance est zéro.
Aucune modification de C#9.
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
static void F1(PublicConstructor s1 = new()) { } // error: default value must be constant
static void F2(PrivateConstructor s2 = new()) { } // ok: initobj
Contraintes de paramètre de type : new()
et struct
Les contraintes de paramètre de type new()
et struct
nécessitent que le constructeur sans paramètre soit public
s’il est défini (voir Contraintes satisfaisantes - §8.4.5).
Le compilateur suppose que tous les structs répondent aux contraintes new()
et struct
.
Aucune modification de C#9.
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct InternalConstructor { internal InternalConstructor() { } }
static T CreateNew<T>() where T : new() => new T();
static T CreateStruct<T>() where T : struct => new T();
_ = CreateNew<PublicConstructor>(); // ok
_ = CreateStruct<PublicConstructor>(); // ok
_ = CreateNew<InternalConstructor>(); // compiles; may fail at runtime
_ = CreateStruct<InternalConstructor>(); // compiles; may fail at runtime
new T()
est émis en tant qu’appel à System.Activator.CreateInstance<T>()
, et le compilateur part du principe que l’implémentation de CreateInstance<T>()
appelle le constructeur sans paramètre public
s’il est défini.
With .NET Framework, Activator.CreateInstance<T>()
appelle le constructeur sans paramètre si la contrainte est where T : new()
mais semble ignorer le constructeur sans paramètre si la contrainte est where T : struct
.
Paramètres facultatifs
Les constructeurs avec des paramètres facultatifs ne sont pas considérés comme des constructeurs sans paramètre. Aucune modification de C#9.
struct S1 { public S1(string s = "") { } }
struct S2 { public S2(params object[] args) { } }
_ = new S1(); // ok: ignores constructor
_ = new S2(); // ok: ignores constructor
Métadonnées
Les constructeurs d’instances de struct sans paramètre explicites seront émis dans les métadonnées.
Les constructeurs d’instance de struct sans paramètre public seront importés à partir de métadonnées ; Les constructeurs d’instances de struct non publics sont ignorés. Aucune modification de C#9.
Voir aussi
Concevoir des réunions
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-04-28.md#open-questions-in-record-and-parameterless-structs
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-03-10.md#parameterless-struct-constructors
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-01-27.md#field-initializers
C# feature specifications