Dela via


Parameterlösa strukturkonstruktorer

Notera

Den här artikeln är en funktionsspecifikation. Specifikationen fungerar som designdokument för funktionen. Den innehåller föreslagna specifikationsändringar, tillsammans med information som behövs under utformningen och utvecklingen av funktionen. Dessa artiklar publiceras tills de föreslagna specifikationsändringarna har slutförts och införlivats i den aktuella ECMA-specifikationen.

Det kan finnas vissa skillnader mellan funktionsspecifikationen och den slutförda implementeringen. Dessa skillnader samlas in i de relevanta anteckningarna från LDM (möte om språkdesign).

Du kan läsa mer om processen för att införa funktionsspecifikationer i C#-språkstandarden i artikeln om specifikationerna.

Champion-problem: https://github.com/dotnet/csharplang/issues/99

Sammanfattning

Stöd för parameterlösa konstruktorer och instansfältsinitierare för strukturegenskaper.

Motivation

Explicita parameterlösa konstruktorer skulle ge mer kontroll över minimalt konstruerade instanser av struct-typen. Instansfältinitierare skulle tillåta förenklad initiering mellan flera konstruktorer. Tillsammans skulle dessa överbrygga en uppenbar klyfta mellan struct och class förklaringar.

Stöd för fältinitierare skulle också tillåta initiering av fält i record struct-deklarationer utan att uttryckligen implementera den primära konstruktorn.

record struct Person(string Name)
{
    public object Id { get; init; } = GetNextId();
}

Om struct-fältinitierare stöds för konstruktorer med parametrar verkar det naturligt att utöka det även till parameterlösa konstruktorer.

record struct Person()
{
    public string Name { get; init; }
    public object Id { get; init; } = GetNextId();
}

Förslag

Instansfältinitierare

Instansfältdeklarationer för en struct kan innehålla initialiserare.

Precis som med klassfältinitierare §15.5.6.3:

En variabelinitierare för ett instansfält kan inte referera till den instans som skapas.

Ett fel rapporteras om en struct har fältinitierare och inga deklarerade instanskonstruktorer eftersom fältinitierarna inte körs.

struct S { int F = 42; } // error: 'struct' with field initializers must include an explicitly declared constructor

Konstruktörer

En struct kan deklarera en parameterlös instanskonstruktor.

En parameterlös instanskonstruktor är giltig för alla structtyper, inklusive struct, readonly struct, ref structoch record struct.

Om ingen parameterlös instanskonstruktor deklareras, strukturen (se §16.4.9) ...

implicit har en parameterlös instanskonstruktor som alltid returnerar värdet som är resultatet av att ange alla värdetypsfält till deras standardvärde och alla referenstypfält till null.

Modifierare

En struct-konstruktor för parameterlös instans måste deklareras public.

struct S0 { }                   // ok
struct S1 { public S1() { } }   // ok
struct S2 { internal S2() { } } // error: parameterless constructor must be 'public'

Icke-offentliga konstruktorer ignoreras vid import av typer från metadata.

Konstruktorer kan deklareras som extern eller unsafe. Konstruktorer kan inte vara partial.

Exekvera fältinitierare

Instansvariabelinitierare (§15.11.3) ändras enligt följande:

När en klass instanskonstruktör inte har någon konstruktorinitierare, eller om den har en konstruktorinitierare i form base(...), utför konstruktorn implicit de initialiseringar som anges av variable_initializerför instansfälten som deklarerats i klassen. Detta motsvarar en sekvens med tilldelningar som körs omedelbart när man går in i konstruktorn och före det implicita anropet av den direkta basklasskonstruktorn.

När en struct-instans-konstruktor inte har någon konstruktor-initierare utför konstruktorn implicit de initieringar som anges av variable_initializerför instansfälten som deklarerats i dess struct. Detta motsvarar en sekvens med tilldelningar som körs omedelbart vid inmatning till konstruktorn.

När en struct-instanskonstruktor har en this() konstruktorinitierare som representerar den standardparameterlösa konstruktornrensar den deklarerade konstruktorn implicit alla instansfält och utför de initieringar som anges av variable_initializerför de instansfält som deklarerats i dess struct. Omedelbart efter inmatningen till konstruktorn anges alla värdetypsfält till standardvärdet och alla fält av referenstyp är inställda på null. Omedelbart därefter körs en sekvens med tilldelningar som motsvarar variable_initializers.

Bestämd tilldelning

Instansfält (förutom fixed-fält) måste vara tydligt tilldelade i struct instanskonstruktorer som inte har en this()-initialiserare (se §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; }
}

Ingen base() initialiserare

En base() initialiserare tillåts inte i strukturskonstruktörer.

Kompilatorn skickar inte ett anrop till baskonstruktorn System.ValueType från struct-instanskonstruktorer.

record struct

Ett fel rapporteras om en record struct har fältinitierare och inte innehåller någon primär konstruktor eller några instanskonstruktorer eftersom fältinitierarna inte körs.

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

En record struct med en tom parameterlista har en primär konstruktor utan parameter.

record struct R3();                // primary .ctor: public R3() { }
record struct R4() { int F = 42; } // primary .ctor: public R4() { F = 42; }

En explicit parameterlös konstruktor i en record struct måste ha en this initierare som anropar den primära konstruktorn eller en uttryckligen deklarerad konstruktor.

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

Fält

Den implicit definierade parameterlösa konstruktorn nollar fält i stället för att anropa några parameterlösa konstruktorer för fälttyperna. Inga varningar rapporteras om att fältkonstruktorer ignoreras. Ingen ändring från 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 uttryck

default ignorerar den parameterlösa konstruktorn och genererar en nollad instans. Ingen ändring från C#9.

// struct S { public S() { } }

_ = default(S); // constructor ignored, no warning

new()

Skapande av objekt anropar den parameterlösa konstruktorn om den är offentlig. annars nollställs instansen. Ingen ändring från C#9.

// public struct PublicConstructor { public PublicConstructor() { } }
// public struct PrivateConstructor { private PrivateConstructor() { } }

_ = new PublicConstructor();  // call PublicConstructor::.ctor()
_ = new PrivateConstructor(); // initobj PrivateConstructor

En varningsvåg kan rapportera en varning för användning av new() med en structtyp som har konstruktorer men ingen parameterlös konstruktor. Ingen varning rapporteras när en sådan structtyp ersätts med en typparameter med en new() eller struct begränsning.

struct S { public S(int i) { } }
static T CreateNew<T>() where T : new() => new T();

_ = new S();        // no warning called
_ = CreateNew<S>(); // ok

Uninitialiserade värden

Ett lokalt eller ett fält av en strukturtyp som inte uttryckligen initieras nollställs. Kompilatorn rapporterar ett definitivt tilldelningsfel för en ennitialiserad struct som inte är tom. Ingen ändring från 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)

Matrisallokering

Matrisallokering ignorerar alla parameterlösa konstruktorer och genererar nollade element. Ingen ändring från C#9.

// struct S { public S() { } }

var a = new S[1]; // constructor ignored, no warning

Parameterstandardvärde new()

Ett parameterstandardvärde för new() binder till den parameterlösa konstruktorn om det är offentligt (och rapporterar ett fel om att värdet inte är konstant); annars nollställs instansen. Ingen ändring från 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

Ange parameterbegränsningar: new() och struct

Parameterbegränsningarna new() och struct kräver att den parameterlösa konstruktorn är public om den är definierad (se tillfredsställande begränsningar – §8.4.5).

Kompilatorn förutsätter att alla structs uppfyller new() och struct begränsningar. Ingen ändring från 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() skickas som ett anrop till System.Activator.CreateInstance<T>(), och kompilatorn förutsätter att implementeringen av CreateInstance<T>() anropar public parameterlös konstruktor om den definieras.

Med .NET Framework anropar Activator.CreateInstance<T>() den parameterlösa konstruktorn om villkoret är where T : new() men verkar ignorera den parameterlösa konstruktorn om villkoret är where T : struct.

Valfria parametrar

Konstruktorer med valfria parametrar betraktas inte som parameterlösa konstruktorer. Ingen ändring från 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

Metadata

Explicita parameterlösa strukturinstanskonstruktorer kommer att genereras till metadata.

Konstruktorer för offentliga parameterlösa struct-instanser importeras från metadata. icke-offentliga struct-instanskonstruktorer ignoreras. Ingen ändring från C#9.

Se även

Designa möten