Parameterlose Strukturkonstruktoren
Hinweis
Dieser Artikel ist eine Feature-Spezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.
Es kann einige Abweichungen zwischen der Feature-Spezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.
Weitere Informationen über den Prozess zur Aufnahme von Feature Speclets in den C#-Sprachstandard finden Sie in dem Artikel über die Spezifikationen.
Champion Issue: https://github.com/dotnet/csharplang/issues/99
Zusammenfassung
Unterstützung von parameterlosen Konstruktoren und Instanzfeld-Initialisierern für Struct-Typen.
Motivation
Explizite parameterlose Konstruktoren würden mehr Kontrolle über minimal konstruierte Instanzen des Struct-Typs bieten.
Instanzfeldinitialisierer würden eine vereinfachte Initialisierung über mehrere Konstruktoren hinweg ermöglichen.
Zusammen würden sie eine offensichtliche Lücke schließen zwischen struct
und class
Erklärungen.
Die Unterstützung für Feldinitialisierer würde auch die Möglichkeit bieten, Felder in record struct
-Deklarationen zu initialisieren, ohne den primären Konstruktor explizit zu implementieren.
record struct Person(string Name)
{
public object Id { get; init; } = GetNextId();
}
Wenn Struct-Feldinitialisierer für Konstruktoren mit Parametern unterstützt werden, liegt es nahe, dies auch auf parameterlose Konstruktoren auszuweiten.
record struct Person()
{
public string Name { get; init; }
public object Id { get; init; } = GetNextId();
}
Vorschlag
Instanzfeld-Initialisierer
Instanzfelddeklarationen für eine Struktur können Initialisierer enthalten.
Wie bei Klassenfeldinitialisierern §15.5.6.3:
Ein Variableninitialisierer für ein Instanzfeld kann nicht auf die zu erstellende Instanz verweisen.
Es wird ein Fehler geliefert, wenn eine Struct Feldinitialisierer und keine deklarierten Instanz-Konstruktoren hat, da die Feldinitialisierer nicht ausgeführt werden.
struct S { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor
Konstruktoren
Eine Struct kann einen parameterlosen Instanz-Konstruktor deklarieren.
Ein parameterloser Instanz-Konstruktor ist für alle Struct-Typen einschließlich struct
, readonly struct
, ref struct
und record struct
gültig.
Wenn kein parameterloser Instanzkonstruktor deklariert ist, wird die Struktur (siehe §16.4.9) ...
Ein impliziter parameterloser Instanzkonstruktor gibt immer den Wert zurück, der sich daraus ergibt, dass alle Felder des Werttyps auf ihren Standardwert und alle Felder des Verweistyps auf null gesetzt werden.
Zusatztasten
Der parameterlose Konstruktor einer Instanzstruktur muss als public
deklariert werden.
struct S0 { } // ok
struct S1 { public S1() { } } // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'
Nicht-öffentliche Konstruktoren werden beim Importieren von Typen aus Metadaten ignoriert.
Konstruktoren können deklariert werden extern
oder unsafe
.
Konstrukteure können nicht partial
.
Ausführen von Feldinitialisierungen
Instanzvariablen-Initialisierer (§15.11.3) is geändert wie folgt:
Wenn eine Klasse Instanzkonstruktor hat keinen Konstruktorinitialisierer, oder er hat einen Konstruktorinitialisierer der Form
base(...)
, Dieser Konstruktor führt implizit die Initialisierungen durch, die in der Variablen_Initialisierers der in seiner Klasse deklarierten Instanzfelder. Dies entspricht einer Folge von Zuweisungen, die unmittelbar nach dem Eintritt in den Konstruktor und vor dem impliziten Aufruf des direkten Basisklassenkonstruktors ausgeführt werden.Wenn ein struct-Instanzkonstruktor keinen Konstruktorinitialisierer hat, führt dieser Konstruktor implizit die Initialisierungen aus, die durch die Variablen_Initialisierers der in seiner Struktur deklarierten Instanzfelder. Dies entspricht einer Folge von Zuweisungen, die unmittelbar beim Eintritt in den Konstruktor ausgeführt werden.
Wenn ein struct-Instanzkonstruktor eine
this()
Konstruktor-Initialisierer, der die Standard-Konstruktor ohne Parameter, der deklarierte Konstruktor löscht implizit alle Instanzfelder und führt die Initialisierungen durch, die in der Variablen_Initialisierers der in seiner Struktur deklarierten Instanzfelder. Unmittelbar beim Eintritt in den Konstruktor werden alle Felder vom Typ Wert auf ihren Standardwert und alle Felder vom Typ Verweis aufnull
. Unmittelbar danach wird eine Folge von Zuweisungen entsprechend der Variablen_Initialisierers ausgeführt werden.
Definite assignment (Festgelegte Zuweisung)
Instanzfelder (außer fixed
-Feldern) müssen in Strukturinstanzkonstruktoren, die keinen this()
-Initialisierer haben, definitiv zugewiesen werden (siehe §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; }
}
Nein base()
Initialisierungsprogramm
A base()
Initialisierer ist in struct-Konstruktoren nicht erlaubt.
Der Compiler gibt keinen Aufruf an die Basis System.ValueType
Konstruktor von struct-Instanzkonstruktoren.
record struct
Ein Fehler wird gemeldet, wenn ein record struct
hat Feldinitialisierungen und enthält weder einen Primärkonstruktor noch Instanzkonstruktoren, da die Feldinitialisierungen nicht ausgeführt werden.
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
Ein record struct
mit einer leeren Parameterliste hat einen parameterlosen primären Konstruktor.
record struct R3(); // primary .ctor: public R3() { }
record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; }
Ein expliziter parameterloser Konstruktor in einem record struct
muss einen this
Initialisierer haben, der den primären Konstruktor oder einen explizit deklarierten Konstruktor aufruft.
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;
}
Felder
Der implizit definierte parameterlose Konstruktor setzt die Felder auf null, anstatt parameterlose Konstruktoren für die Feldtypen aufzurufen. Es werden keine Warnungen gemeldet, dass Feldkonstruktoren ignoriert werden. Keine Änderung gegenüber 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
Ausdruck
default
ignoriert den parameterlosen Konstruktor und generiert eine Instanz mit Nullen.
Keine Änderung gegenüber C#9.
// struct S { public S() { } }
_ = default(S); // constructor ignored, no warning
new()
Bei der Objekterstellung wird der parameterlose Konstruktor aufgerufen, wenn er öffentlich ist; andernfalls wird die Instanz auf Null gesetzt. Keine Änderung gegenüber C#9.
// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }
_ = new PublicConstructor(); // call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // initobj PrivateConstructor
Eine Warnwelle kann eine Warnung melden für die Verwendung von new()
mit einem struct-Typ, der Konstruktoren, aber keinen parameterlosen Konstruktor hat.
Es wird keine Warnung ausgegeben, wenn ein solcher Strukturtyp als Ersatz für einen Typparameter mit einer new()
oder struct
Einschränkung.
struct S { public S(int i) { } }
static T CreateNew<T>() where T : new() => new T();
_ = new S(); // no warning called
_ = CreateNew<S>(); // ok
Uninitialisierte Werte
Ein lokales oder Feld eines Strukturtyps, das nicht explizit initialisiert ist, wird auf Null gesetzt. Der Compiler liefert einen definitiven Zuweisungsfehler für ein nicht initialisiertes Struct, das nicht leer ist. Keine Änderung gegenüber 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)
Array-Zuordnung
Die Zuweisung von Arrays ignoriert jeden parameterlosen Konstruktor und generiert nullte Elemente. Keine Änderung gegenüber C#9.
// struct S { public S() { } }
var a = new S[1]; // constructor ignored, no warning
Parameter-Standardwert new()
Ein Parameterstandardwert von new()
bindet an den parameterlosen Konstruktor, wenn er öffentlich ist (und meldet einen Fehler, dass der Wert nicht konstant ist); andernfalls wird die Instanz auf null gesetzt.
Keine Änderung gegenüber 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
Typ-Parameter-Einschränkungen: new()
und struct
Die new()
- und struct
-Typparametereinschränkungen erfordern, dass der parameterlose Konstruktor bei seiner Definition public
sein muss (siehe Zufriedenstellende Einschränkungen - §8.4.5).
Der Compiler setzt voraus, dass alle Strukturen die new()
- und struct
-Beschränkungen erfüllen.
Keine Änderung gegenüber 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()
wird als ein Aufruf an System.Activator.CreateInstance<T>()
ausgegeben, und der Compiler geht davon aus, dass die Implementierung von CreateInstance<T>()
, falls definiert, den parameterlosen Konstruktor von public
aufruft.
Mit dem .NET Framework ruft Activator.CreateInstance<T>()
den parameterlosen Konstruktor auf, wenn die Einschränkung where T : new()
ist, ignoriert ihn jedoch, wenn die Einschränkung where T : struct
ist.
Optionale Parameter
Konstruktoren mit optionalen Parametern werden nicht als parameterlose Konstruktoren betrachtet. Keine Änderung gegenüber 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
Metadaten
Explizite parameterlose struct-Instanzkonstruktoren werden an die Metadaten ausgegeben.
Öffentliche parameterlose Struct-Instanz-Konstruktoren werden aus den Metadaten importiert; nicht-öffentliche Struct-Instanz-Konstruktoren werden ignoriert. Keine Änderung gegenüber C#9.
Weitere Informationen
Entwurfsbesprechungen
- 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