Condividi tramite


Costruttori di struct senza parametri

Nota

Questo articolo è una specifica delle funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.

Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono acquisite nelle note language design meeting (LDM) pertinenti.

Ulteriori informazioni sul processo di adozione degli speclet delle funzionalità nel linguaggio C# standard sono disponibili nell'articolo sulle specifiche di .

Problema del campione: https://github.com/dotnet/csharplang/issues/99

Sommario

Supportare i costruttori senza parametri e gli inizializzatori dei campi di istanza per i tipi di struct.

Motivazione

I costruttori espliciti senza parametri offrono un maggiore controllo sulle istanze costruite in modo minimo del tipo di struct. Gli inizializzatori di campi dell'istanza consentono l'inizializzazione semplificata tra più costruttori. Insieme, questi chiuderebbero un divario evidente tra le dichiarazioni struct e class.

Il supporto per gli inizializzatori di campo consente anche l'inizializzazione dei campi nelle dichiarazioni di record struct senza implementare in modo esplicito il costruttore primario.

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

Se gli inizializzatori di campo struct sono supportati per i costruttori con parametri, sembra naturale estenderlo anche ai costruttori senza parametri.

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

Proposta

Inizializzatori di campi dell'istanza

Le dichiarazioni di campo di istanza per uno struct possono includere inizializzatori.

Come per gli inizializzatori di campo di classe §15.5.6.3:

Un inizializzatore di variabile per un campo dell'istanza non può fare riferimento all'istanza creata.

Viene segnalato un errore se una struct ha inizializzatori di campo e non ha costruttori di istanza dichiarati, poiché non verranno eseguiti.

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

Costruttori

Uno struct può dichiarare un costruttore di istanza senza parametri.

Un costruttore di istanza senza parametri è valido per tutti i tipi di struct, inclusi struct, readonly struct, ref structe record struct.

Se non viene dichiarato alcun costruttore di istanza senza parametri, la struttura (vedere §16.4.9) ...

ha in modo implicito un costruttore di istanza senza parametri che restituisce sempre il valore risultante dall'impostazione di tutti i campi del tipo di valore al loro valore predefinito e di tutti i campi del tipo di riferimento a null.

Modificatori

Un costruttore di istanza struct senza parametri deve essere dichiarato public.

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

I costruttori non pubblici vengono ignorati durante l'importazione di tipi dai metadati.

I costruttori possono essere dichiarati extern o unsafe. I costruttori non possono essere partial.

Esecuzione di inizializzatori di campo

Inizializzatore di variabile di istanza (§15.11.3) viene modificato come indicato di seguito:

Quando una classe costruttore dell'istanza non dispone di un inizializzatore del costruttore oppure ha un inizializzatore del costruttore del modulo base(...), tale costruttore esegue in modo implicito le inizializzazioni specificate dal variable_initializerdei campi dell'istanza dichiarati nella relativa classe. Corrisponde a una sequenza di assegnazioni eseguite immediatamente dopo l'immissione al costruttore e prima della chiamata implicita del costruttore della classe base diretta.

Quando un costruttore di istanza di struct non dispone di un inizializzatore del costruttore, tale costruttore esegue in modo implicito le inizializzazioni specificate dal variable_initializerdei campi dell'istanza dichiarati nel relativo struct. Corrisponde a una sequenza di assegnazioni eseguite immediatamente all'ingresso del costruttore.

Quando un costruttore di istanza di struct dispone di un inizializzatore del costruttore this() che rappresenta il costruttore predefinito senza parametri , il costruttore dichiarato cancella in modo implicito tutti i campi dell'istanza ed esegue le inizializzazioni specificate dal variable_initializerdei campi dell'istanza dichiarati nel relativo struct. Immediatamente all'ingresso nel costruttore, tutti i campi di tipo valore vengono impostati sul valore predefinito e tutti i campi del tipo di riferimento vengono impostati su null. Subito dopo, viene eseguita una sequenza di assegnazioni corrispondenti alle variabili inizializzatrici s.

Assegnazione definita

I campi dell'istanza (diversi dai campi fixed) devono essere assegnati in modo definitivo nei costruttori di istanza di struct che non dispongono di un inizializzatore this() (vedere §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; }
}

Nessun inizializzatore di base()

Un inizializzatore base() non è consentito nei costruttori di struct.

Il compilatore non genererà una chiamata al costruttore di base System.ValueType dai costruttori dell'istanza di struct.

record struct

Viene segnalato un errore se un record struct ha inizializzatori di campo e non contiene un costruttore primario né costruttori di istanza poiché gli inizializzatori di campo non verranno eseguiti.

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

Un record struct con un elenco di parametri vuoto avrà un costruttore primario senza parametri.

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

Un costruttore esplicito senza parametri in un record struct deve avere un inizializzatore this che chiama il costruttore primario o un costruttore dichiarato in modo esplicito.

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

Campi

Il costruttore implicito senza argomenti azzererà i campi invece di chiamare i costruttori senza argomenti dei tipi di campo. Non ci sono segnalazioni di avvisi che i costruttori di campo vengono ignorati. Nessuna modifica da 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
}

espressione default

default ignora il costruttore senza parametri e genera un'istanza azzerata. Nessuna modifica da C#9.

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

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

new()

La creazione dell'oggetto richiama il costruttore senza parametri se è pubblico; in caso contrario, l'istanza viene azzerata. Nessuna modifica da C#9.

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

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

Un messaggio di avviso può segnalare un avviso per l'uso di new() con un tipo di struct che ha costruttori ma nessun costruttore senza parametri. Non verrà segnalato alcun avviso quando si usa la sostituzione di tale tipo di struct per un parametro di tipo con un vincolo new() o struct.

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

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

Valori non inizializzati

Una variabile locale o un campo di un tipo struct che non è inizializzato in modo esplicito è azzerato. Il compilatore segnala un errore di assegnazione definito per uno struct non inizializzato che non è vuoto. Nessuna modifica da 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)

Allocazione di matrici

L'allocazione di matrici ignora qualsiasi costruttore senza parametri e genera elementi azzerati. Nessuna modifica da C#9.

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

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

Valore predefinito del parametro new()

Un valore predefinito del parametro di new() viene associato al costruttore senza parametri se public (e segnala un errore che indica che il valore non è costante); in caso contrario, l'istanza è zero. Nessuna modifica da 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

Vincoli dei parametri di tipo: new() e struct

I vincoli dei parametri di tipo new() e struct richiedono che il costruttore senza parametri sia public se definito (vedere Vincoli soddisfacenti - §8.4.5).

Il compilatore presuppone che tutti gli struct soddisfino i vincoli new() e struct. Nessuna modifica da 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() viene emesso come chiamata a System.Activator.CreateInstance<T>()e il compilatore presuppone che l'implementazione di CreateInstance<T>() invochi il costruttore senza parametri di public, se definito.

Con .NET Framework, Activator.CreateInstance<T>() richiama il costruttore senza parametri se il vincolo è where T : new() ma sembra ignorare il costruttore senza parametri se il vincolo è where T : struct.

Parametri facoltativi

I costruttori con parametri facoltativi non sono considerati costruttori senza parametri. Nessuna modifica da 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

Metadati

I costruttori espliciti di istanze di struct senza parametri verranno generati nei metadati.

I costruttori di struct senza parametri pubblici verranno importati dai metadati; I costruttori di istanza di struct non pubblici verranno ignorati. Nessuna modifica da C#9.

Vedere anche

Riunioni di progettazione