Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
16.1 Generale
Gli struct sono simili alle classi in quanto rappresentano strutture di dati che possono contenere membri dati e membri di funzione. Tuttavia, a differenza delle classi, gli struct sono tipi valore e non richiedono l'allocazione dell'heap. Una variabile di un struct tipo contiene direttamente i dati di struct, mentre una variabile di un tipo di classe contiene un riferimento ai dati, quest'ultimo noto come oggetto .
Nota: gli struct sono particolarmente utili per piccole strutture di dati con semantica di valore. I numeri complessi, i punti di un sistema di coordinate o le coppie chiave-valore di un dizionario sono buoni esempi di struct. La chiave per queste strutture di dati è che dispongono di pochi membri dati, che non richiedono l'uso dell'ereditarietà o della semantica di riferimento, ma possono essere implementati facilmente usando la semantica dei valori in cui l'assegnazione copia il valore anziché il riferimento. nota finale
Come descritto in §8.3.5, i tipi semplici forniti da C#, ad esempio int, doublee bool, sono, infatti, tutti i tipi di struct.
16.2 Dichiarazioni di struct
16.2.1 Generale
Un struct_declaration è un type_declaration (§14.8) che dichiara un nuovo 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 ';'?
| ';'
;
Un struct_declaration è relativo a uno struct non di record o a uno struct di record.
Un non_record_struct_declaration è costituito da un set facoltativo di attributi (§23), seguito da un set facoltativo di struct_modifiers (§16.2.2), seguito da un modificatore facoltativo ref (§16.2.3), seguito da un modificatore parziale facoltativo (§15.2.7), seguito dalla parola chiave struct e da un identificatore che denomina lo struct, seguito da una specifica type_parameter_list facoltativa (§15.2.3), seguito da una specifica facoltativa di struct_interfaces (§16.2.5), seguita da una specifica facoltativa di type_parameter_constraints clausole (§15.2.5), seguita da un struct_body (§16.2.6), facoltativamente seguito da un punto e virgola.
Un record_struct_declaration è costituito da un set facoltativo di attributi (§23), seguito da un set facoltativo di struct_modifiers (§16.2.2), seguito da un modificatore parziale facoltativo (§15.2.7), seguito dalla parola chiave , seguito dalla parola chiave recordstruct e da un identificatore che denomina lo struct, seguito da una specifica facoltativa type_parameter_list (§15.2.3), seguita da un delimited_parameter_list facoltativo specifica (§15.2.1), seguita da una specifica struct_interfaces facoltativa (§16.2.5), seguita da una specifica facoltativa type_parameter_constraints clausole (§15.2.5), seguita da un record_struct_body.
Un struct_declaration non deve fornire type_parameter_constraints_clausea meno che non fornisca anche un type_parameter_list.
Un struct_declaration che fornisce un type_parameter_list è una dichiarazione di struct generica. Inoltre, qualsiasi struct annidato all'interno di una dichiarazione di classe generica o una dichiarazione di struct generica è una dichiarazione di struct generica, poiché gli argomenti di tipo per il tipo contenitore devono essere forniti per creare un tipo costruito (§8.4).
Un non_record_struct_declaration che include un ref modificatore non deve avere una parte struct_interfaces .
Un record_struct_declaration con un delimited_parameter_list dichiara uno struct di record posizionale.
Al massimo un solo record_struct_declaration contenente partial può fornire un delimited_parameter_list.
I parametri in delimited_parameter_list non devono contenere ref, out o this modificatori, ma inparams sono consentiti modificatori.
Per un record_struct_declaration, le record_struct_bodys {}, {};e ; sono equivalenti. Tutti indicano che gli unici membri sono quelli sintetizzati dal compilatore (§16.4).
16.2.2 Modificatori di struct
Un struct_declaration può facoltativamente includere una sequenza di struct_modifiers:
struct_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'readonly'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§24.2) è disponibile solo nel codice unsafe (§24).
È un errore di compilazione che lo stesso modificatore appaia più volte in una dichiarazione di struct.
readonlyAd eccezione di , i modificatori di una dichiarazione di struct hanno lo stesso significato di quelli di una dichiarazione di classe (§15.2.2).
Il readonly modificatore indica che il struct_declaration dichiara un tipo le cui istanze non sono modificabili.
Uno struct readonly presenta i vincoli seguenti:
- Ogni campo dell'istanza deve essere dichiarato
readonlyanche. - Non deve dichiarare eventi di tipo campo (§15.8.2).
Quando un'istanza di uno struct readonly viene passata a un metodo, viene this considerata come un argomento/parametro di input, che non consente l'accesso in scrittura a tutti i campi dell'istanza (ad eccezione dei costruttori).
16.2.3 Modificatore di riferimento
Il ref modificatore indica che il non_record_struct_declaration dichiara un tipo le cui istanze vengono allocate nello stack di esecuzione. Questi tipi sono chiamati ref struct types. Il ref modificatore dichiara che le istanze possono contenere campi simili ai riferimenti e non devono essere copiate dal contesto sicuro (§16.5.15). Le regole per determinare il contesto sicuro di uno struct di riferimento sono descritte in §16.5.15.
Si tratta di un errore in fase di compilazione se viene usato un tipo di struct ref in uno dei contesti seguenti:
- Come tipo di elemento di una matrice.
- Come tipo dichiarato di un campo di una classe o di uno struct che non dispone del
refmodificatore. - Come argomento di tipo.
- Come tipo di elemento di una tupla.
- In un metodo asincrono.
- In un iteratore.
- Come tipo di ricevitore per la conversione di un gruppo di metodi da un metodo di istanza a un tipo delegato.
- Come variabile acquisita in un'espressione lambda o in una funzione locale.
Inoltre, le restrizioni seguenti si applicano a un ref struct tipo:
- Un
ref structtipo non deve essere sottoposto a boxing inSystem.ValueTypeoSystem.Object. - Un
ref structtipo non deve essere dichiarato per implementare alcuna interfaccia. - Un metodo di istanza dichiarato in
objecto inSystem.ValueTypema non sottoposto a override in un tiporef structnon deve essere chiamato con un ricevitore di quel tiporef struct.
Nota: un
ref structoggetto non dichiaraasyncmetodi di istanza né usa un'istruzioneyield returnoyield breakall'interno di un metodo di istanza, perché il parametro implicitothisnon può essere usato in tali contesti. nota finale
Questi vincoli assicurano che una variabile di ref struct tipo non faccia riferimento alla memoria dello stack non più valida o alle variabili non più valide.
16.2.4 Modificatore parziale
Il partial modificatore indica che questo struct_declaration è una dichiarazione di tipo parziale. Più dichiarazioni parziali di struct con lo stesso nome all'interno di un namespace o di una dichiarazione di tipo circostante si combinano per formare una sola dichiarazione di struct, seguendo le regole specificate in §15.2.7.
16.2.5 Interfacce Struct
Una dichiarazione di struct può includere una specifica struct_interfaces , nel qual caso lo struct viene detto di implementare direttamente i tipi di interfaccia specificati. Per un tipo struct costruito, incluso un tipo annidato dichiarato all'interno di una dichiarazione di tipo generico (§15.3.9.7), ogni tipo di interfaccia implementato viene ottenuto sostituendo, per ogni type_parameter nell'interfaccia specificata, il type_argument corrispondente del tipo costruito.
struct_interfaces
: ':' interface_type_list
;
La gestione delle interfacce su più parti di una dichiarazione di struct parziale (§15.2.7) è illustrata più avanti in §15.2.4.3.
Le implementazioni dell'interfaccia sono illustrate più avanti in §19.6.
Corpo Struct 16.2.6
Il struct_body di uno struct definisce i membri dello struct.
struct_body
: '{' struct_member_declaration* '}'
;
16.3 Membri della Struttura
16.3.1 Generale
I membri di uno struct sono costituiti dai membri introdotti dai relativi struct_member_declaratione dai membri ereditati dal tipo System.ValueType. Per uno struct di record, il set di membri include anche i membri sintetizzati generati dal compilatore (§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) è disponibile solo nel codice non sicuro (§24).
Nota: Tutti i tipi di class_member_declaration ad eccezione di finalizer_declaration sono anche struct_member_declaration. nota finale
Ad eccezione delle differenze indicate in §16.5, le descrizioni dei membri della classe fornite anche in §15.3 a §15.12 si applicano anche ai membri dello struct.
Si tratta di un errore per un campo di istanza di uno struct di record per avere un tipo non sicuro.
16.3.2 Membri readonly
Una definizione o una funzione di accesso del membro dell'istanza di una proprietà dell'istanza, un indicizzatore o un evento che include il readonly modificatore presenta le restrizioni seguenti:
- Il
thisparametro è unref readonlyriferimento. - Il membro non riassegna il valore di o un campo di
thisistanza del ricevitore. - Il membro non riassegna il valore di un evento di tipo campo dell'istanza (§15.8.2) del ricevitore.
- Se un membro readonly richiama un membro non di sola lettura, la struttura a
thiscui fa riferimento deve essere copiata per utilizzare un riferimento scrivibile per l'argomentothis.
Nota: I campi dell'istanza includono il campo sottostante nascosto utilizzato per le proprietà implementate automaticamente (§15.7.4). nota finale
Esempio: un membro readonly può modificare lo stato di un oggetto a cui fa riferimento un campo di istanza, anche se il membro readonly non può riassegnare tale membro dell'istanza. Il codice seguente illustra la riassegnazione e la modifica di un campo di istanza:
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); } }Il
readonlymetodoAddMessagepuò modificare lo stato di un elenco di messaggi. IlInitializeMessagesmembro può cancellare e inizializzare nuovamente l'elenco dei messaggi. Nel caso diAddMessage, ilreadonlymodificatore è valido. Nel caso diInitializeMessages, l'aggiunta delreadonlymodificatore non è valida. esempio finale
16.4 Membri dello struct di record sintetizzato
16.4.1 Generale
Nel caso di uno struct di record, i membri vengono sintetizzati a meno che non venga ereditato un membro con una firma "corrispondente" nel record_struct_body o un membro non virtuale non virtuale accessibile con una firma "corrispondente". Due membri vengono considerati corrispondenti se hanno la stessa firma o vengono considerati "nascosti" in uno scenario di ereditarietà. Vedere Firme e overload di §7.6.
I membri sintetizzati sono descritti nelle sottoclause seguenti.
16.4.2 Membri di uguaglianza
I membri di uguaglianza sintetizzati sono simili a quelli per una classe di record (§15.16.2), ad eccezione della mancanza di EqualityContract, controlli Null o ereditarietà.
Uno struct R di record implementa System.IEquatable<R> e include un overload fortemente tipizzato sintetizzato di Equals(R other), che è pubblico, come indicato di seguito:
public readonly bool Equals(R other);
Questo metodo può essere dichiarato in modo esplicito. Tuttavia, si tratta di un errore se la dichiarazione esplicita non corrisponde alla firma o all'accessibilità prevista.
Se Equals(R other) è definito dall'utente (ovvero non sintetizzato), ma GetHashCode non è, verrà generato un avviso.
Il sintetizzato Equals(R) restituisce true se e solo se per ogni campo fieldN dell'istanza nello struct del record il valore di System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN), dove TN è il tipo di campo, è true.
Lo struct di record include operatori sintetizzati == e != equivalenti agli operatori dichiarati come segue:
public static bool operator==(R r1, R r2) => r1.Equals(r2);
public static bool operator!=(R r1, R r2) => !(r1 == r2);
Il Equals metodo chiamato dall'operatore == è il Equals(R other) metodo specificato in precedenza. L'operatore != delega all'operatore == . Si tratta di un errore se gli operatori vengono dichiarati in modo esplicito.
Lo struct di record include un override sintetizzato equivalente a un metodo dichiarato come segue:
public override readonly bool Equals(object? obj);
Si tratta di un errore se l'override viene dichiarato in modo esplicito. L'override sintetizzato restituirà other is R temp && Equals(temp) dove R è lo struct del record.
Lo struct di record include un override sintetizzato equivalente a un metodo dichiarato come segue:
public override readonly int GetHashCode();
Questo metodo può essere dichiarato in modo esplicito.
Un avviso deve essere segnalato se uno di Equals(R) e GetHashCode() viene dichiarato in modo esplicito, ma l'altro metodo non è.
L'override sintetizzato di restituisce un int risultato della combinazione dei valori di System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN) per ogni campo fieldN dell'istanza con TN il tipo di fieldN.GetHashCode()
Esempio: si consideri lo struct di record seguente:
record struct R1(T1 P1, T2 P2);Per questo motivo, i membri di uguaglianza sintetizzati saranno simili ai seguenti:
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));esempio finale
16.4.3 Membri stampa
Uno struct di record include un metodo sintetizzato equivalente al seguente:
private bool PrintMembers(System.Text.StringBuilder builder);
Questo metodo esegue le attività seguenti:
- Per ogni membro stampabile dello struct di record (campo pubblico non statico e membri di proprietà leggibili), aggiunge il nome del membro seguito da "
=" seguito dal valore del membro separato con ", “, - Restituisce true se lo struct del record dispone di membri stampabili.
Per un membro con un tipo valore, il relativo valore deve essere convertito in una rappresentazione di stringa.
Se i membri stampabili del record non includono una proprietà leggibile con unareadonlyget funzione di accesso non, il sintetizzato PrintMembers è readonly. Non è necessario che i campi del record siano readonly affinché il PrintMembers metodo sia readonly.
Il PrintMembers metodo può essere dichiarato in modo esplicito. Tuttavia, si tratta di un errore se la dichiarazione esplicita non corrisponde alla firma o all'accessibilità prevista.
Lo struct di record include un metodo sintetizzato equivalente al seguente:
public override string ToString();
Se il metodo dello struct del PrintMembers record è readonly, il metodo sintetizzato ToString() sarà readonly.
Questo metodo può essere dichiarato in modo esplicito. Si tratta di un errore se la dichiarazione esplicita non corrisponde alla firma o all'accessibilità prevista.
Questo metodo esegue le attività seguenti:
- Crea un'istanza
StringBuilderdi , - Aggiunge il nome dello struct del record al generatore, seguito da "
{", - Richiama il metodo dello struct del
PrintMembersrecord assegnandolo al generatore, seguito da "" se ha restituito true, - Aggiunge "
}", - Restituisce il contenuto del generatore con
builder.ToString().
Esempio: si consideri lo struct di record seguente:
record struct R1(T1 P1, T2 P2);Per questo struct di record, i membri di stampa sintetizzati saranno simili ai seguenti:
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(); } }esempio finale
16.4.4 Membri dello struct record posizionale
16.4.4.1 Generale
Oltre a fornire i membri descritti nelle sottoclause precedenti, gli struct di record posizionali (§16.2.1) sintetizzano membri aggiuntivi con le stesse condizioni degli altri membri, come descritto nelle sottoclause seguenti.
16.4.4.2 Costruttore primario
Uno struct di record ha un costruttore pubblico la cui firma corrisponde ai parametri di valore della dichiarazione di tipo. Questo metodo viene chiamato costruttore primario per il tipo. Si tratta di un errore per avere un costruttore primario e un costruttore con la stessa firma già presente nello struct. Se la dichiarazione di tipo non include un delimited_parameter_list, non viene generato alcun costruttore primario.
record struct R1 { public R1() { } // OK } record struct R2() { public R2() { } // error: 'R2' already defines // a constructor with the same parameter types }
Le dichiarazioni di campo dell'istanza per uno struct di record possono includere inizializzatori di variabili. Se non è presente alcun costruttore primario, gli inizializzatori di istanza verranno eseguiti come parte del costruttore senza parametri. In caso contrario, in fase di esecuzione il costruttore primario esegue gli inizializzatori di istanza visualizzati nel corpo del record-struct.
Se uno struct di record ha un costruttore primario, qualsiasi costruttore definito dall'utente deve avere un inizializzatore di costruttore esplicito this che chiama il costruttore primario o un costruttore dichiarato in modo esplicito.
I parametri del costruttore primario e i membri dello struct del record sono inclusi nell'ambito all'interno di inizializzatori di campi o proprietà dell'istanza. I membri dell'istanza sono un errore in queste posizioni, ma i parametri del costruttore primario si trovano nell'ambito e possono essere usati e i membri shadow. I membri statici sarebbero utilizzabili anche.
Se un parametro del costruttore primario non viene letto, verrà generato un avviso.
Le regole di assegnazione definite per i costruttori di istanza di struct si applicano al costruttore primario di struct di record. Ad esempio, di seguito è riportato un errore:
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 Proprietà
Per ogni parametro di un delimited_parameter_list con lo stesso nome e tipo di un campo di istanza dichiarato in modo esplicito, la parte restante di questa sottochiave non si applica.
Per ogni parametro struct di record di un delimited_parameter_list è presente un membro della proprietà pubblica corrispondente il cui nome e tipo vengono ricavati dalla dichiarazione del parametro value.
Per uno struct di record:
viene creata una proprietà pubblica
geteinitautomatica se lo struct del record dispone di unreadonlymodificatoregetesetin caso contrario. Entrambi i tipi di funzioni di accesso set (seteinit) sono considerati "corrispondenti". Pertanto, l'utente può dichiarare una proprietà init-only al posto di una sintetizzabile modificabile.Viene eseguito l'override di una proprietà ereditata
abstractcon tipo corrispondente.Non viene creata alcuna proprietà automatica se lo struct del record ha un campo di istanza con il nome e il tipo previsti.
Si tratta di un errore se la proprietà ereditata non dispone
publicdi funzioni di accesso einitset/.getSi tratta di un errore se la proprietà o il campo ereditato è nascosto.
La proprietà automatica viene inizializzata sul valore del parametro del costruttore primario corrispondente.
Gli attributi possono essere applicati alla proprietà automatica sintetizzata e al relativo campo sottostante utilizzando
property:ofield:destinazioni per gli attributi applicati sintatticamente al parametro struct di record corrispondente.
16.4.4.4 Deconstruct
Uno struct di record posizionale con almeno un parametro sintetizza un metodo di istanza pubblico voidrestituito denominato Deconstruct con una dichiarazione di parametro out per ogni parametro della dichiarazione del costruttore primario. Ogni parametro di Deconstruct ha lo stesso tipo del parametro corrispondente della dichiarazione del costruttore primario. Il corpo del metodo assegna ogni parametro del metodo Deconstruct al valore da un membro dell'istanza di accesso a un membro dello stesso nome.
Se i membri dell'istanza a cui si accede nel corpo non includono una proprietà con unareadonlyget funzione di accesso non, il metodo sintetizzato Deconstruct è readonly.
Il metodo può essere dichiarato in modo esplicito. Si tratta di un errore se la dichiarazione esplicita non corrisponde alla firma o all'accessibilità prevista o è statica.
16.5 Differenze tra classi e struct
16.5.1 Generale
Le strutture differiscono dalle classi per vari motivi importanti.
- Gli struct sono tipi valore (§16.5.2).
- Tutti i tipi di struct ereditano in modo implicito dalla classe
System.ValueType(§16.5.3). - L'assegnazione a una variabile di un tipo struct crea una copia del valore assegnato (§16.5.4).
- Il valore predefinito di uno struct è il valore prodotto impostando tutti i campi sul valore predefinito (§16.5.5).
- Le operazioni di conversione boxing e unboxing vengono utilizzate per eseguire la conversione tra un tipo struct e determinati tipi di riferimento (§16.5.6).
- Il significato di è diverso all'interno dei
thismembri dello struct (§16.5.7). - Uno struct non può dichiarare un finalizzatore.
- Le dichiarazioni di evento, le dichiarazioni di proprietà, le funzioni di accesso alle proprietà, le dichiarazioni dell'indicizzatore e le dichiarazioni di metodo possono avere il modificatore
readonly, mentre ciò non è generalmente consentito per gli stessi tipi di membro nelle classi.
16.5.2 Semantica dei valori
Gli struct sono tipi valore (§8.3) e hanno una semantica dei valori. Le classi, d'altra parte, sono tipi di riferimento (§8.2) e hanno una semantica di riferimento.
Una variabile di un tipo struct contiene direttamente i dati dello struct, mentre una variabile di un tipo di classe contiene un riferimento a un oggetto che contiene i dati. Quando uno struct B contiene un campo di istanza di tipo A ed A è un tipo di struct, si tratta di un errore in fase di compilazione per A dipendere da B o da un tipo costruito da B. Un struct Xdipende direttamente da uno structY se X contiene un campo di istanza di tipo Y. Data questa definizione, il set completo di struct da cui un struct dipende è la chiusura transitiva della relazione dipende direttamente da.
Esempio:
struct Node { int data; Node next; // error, Node directly depends on itself }è un errore perché
Nodecontiene un campo di istanza del proprio tipo. Un altro esempiostruct A { B b; } struct B { C c; } struct C { A a; }è un errore perché ognuno dei tipi
A,BeCdipende l'uno dall'altro.esempio finale
Con le classi, è possibile che due variabili facciano riferimento allo stesso oggetto e quindi sia possibile che le operazioni su una variabile influiscano sull'oggetto a cui fa riferimento l'altra variabile. Con gli struct, le variabili hanno la propria copia dei dati (tranne nel caso dei parametri di riferimento) e non è possibile che le operazioni su uno influiscano sull'altro. Inoltre, tranne quando è esplicitamente nullable (§8.3.12), non è possibile che i valori di un tipo struct siano null.
Nota: se uno struct contiene un campo di tipo riferimento, il contenuto dell'oggetto a cui viene fatto riferimento può essere modificato da altre operazioni. Tuttavia, il valore del campo stesso, ovvero l'oggetto a cui fa riferimento, non può essere modificato tramite una mutazione di un valore di struct diverso. nota finale
Esempio: dato quanto segue
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); } }l'output è
10. L'assegnazione diaabcrea una copia del valore ebpertanto non è interessata dall'assegnazione aa.x. SePointfosse invece stato dichiarato come classe, l'output sarebbe100perchéaebfarebbero riferimento allo stesso oggetto.esempio finale
16.5.3 Ereditarietà
Tutti i tipi di struct ereditano in modo implicito dalla classe System.ValueType, che a sua volta eredita dalla classe object. Una dichiarazione di struct può specificare un elenco di interfacce implementate, ma non è possibile che una dichiarazione di struct specifichi una classe di base.
I tipi di struct non sono mai astratti e vengono sempre bloccati in modo implicito. I modificatori abstract e sealed pertanto non sono consentiti in una dichiarazione di struct.
Poiché l'ereditarietà non è supportata per gli struct, l'accessibilità dichiarata di un membro struct non può essere protected, private protectedo protected internal.
I membri funzione in una struttura non possono essere astratti o virtuali e il modificatore override è consentito solo per eseguire l'override dei metodi ereditati da System.ValueType.
16.5.4 Assegnazione
L'assegnazione a una variabile di un tipo struct crea una copia del valore assegnato. Ciò differisce dall'assegnazione a una variabile di un tipo di classe, che copia il riferimento ma non l'oggetto identificato dal riferimento.
Analogamente a un'assegnazione, quando uno struct viene passato come parametro di valore o restituito come risultato di un membro di funzione, viene creata una copia dello struct. Uno struct può essere passato per riferimento a un membro di funzione usando un parametro by-reference.
Quando una proprietà o un indicizzatore di uno struct è la destinazione di un'assegnazione, l'espressione di istanza associata all'accesso alla proprietà o all'indicizzatore deve essere classificata come variabile. Se l'espressione di istanza viene classificata come valore, si verifica un errore in fase di compilazione. Questo è descritto in dettaglio più dettagliatamente in §12.24.2.
16.5.5 Valori predefiniti
Come descritto in §9.3, diversi tipi di variabili vengono inizializzati automaticamente sul valore predefinito al momento della creazione. Per le variabili di tipi di classe e altri tipi di riferimento, questo valore predefinito è null. Tuttavia, poiché gli struct sono tipi valore che non possono essere null, il valore predefinito di uno struct è il valore generato impostando tutti i campi tipo valore sul valore predefinito e su tutti i campi del tipo di riferimento su null.
Esempio: riferimento allo
Pointstruct dichiarato in precedenza, l'esempioPoint[] a = new Point[100];inizializza ogni elemento della matrice al valore prodotto impostando i campi
Pointexsu zero.esempio finale
Il valore predefinito di uno struct corrisponde al valore restituito dal costruttore predefinito dello struct (§8.3.3). Quando uno struct non dichiara un costruttore di istanza senza parametri esplicito, il costruttore predefinito viene sintetizzato e restituisce sempre il valore risultante dall'impostazione di tutti i campi sui valori predefiniti. L'espressione default produce sempre il valore predefinito con inizializzazione zero, anche quando uno struct dichiara un costruttore di istanza senza parametri esplicito (§16.4.9).
Nota: gli struct devono essere progettati per considerare lo stato di inizializzazione predefinito uno stato valido. Nell'esempio
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; } }Il costruttore di istanza definito dall'utente protegge contro i valori
nullsolo quando viene chiamato esplicitamente. Nei casi in cui unaKeyValuePairvariabile è soggetta all'inizializzazione del valore predefinito, i campikeyevaluesarannonull, e la struttura deve essere preparata per gestire questo stato.nota finale
16.5.6 Boxing e unboxing
Un valore di un tipo di classe può essere convertito in tipo object o in un tipo di interfaccia implementato dalla classe semplicemente trattando il riferimento come un altro tipo in fase di compilazione. Analogamente, un valore di tipo object o un valore di un tipo di interfaccia può essere convertito nuovamente in un tipo di classe senza modificare il riferimento ( ma, naturalmente, è necessario un controllo del tipo di runtime in questo caso).
Poiché gli struct non sono tipi di riferimento, queste operazioni vengono implementate in modo diverso per i tipi di struct. Quando un valore di un tipo struct viene convertito in determinati tipi di riferimento (come definito in §10.2.9), viene eseguita un'operazione di incapsulamento (boxing). Analogamente, quando un valore di determinati tipi di riferimento (come definito in §10.3.7) viene convertito nuovamente in un tipo di struct, viene eseguita un'operazione unboxing. Una differenza fondamentale rispetto alle stesse operazioni sui tipi di classe è che le operazioni di boxing e unboxing copiano il valore dello struct nell'istanza boxed o fuori dall'istanza boxed.
Nota: Pertanto, dopo un'operazione di boxing o unboxing, le modifiche apportate all'elemento unboxed
structnon vengono riflesse nell'elemento boxedstruct. nota finale
Per ulteriori informazioni su boxing e unboxing, vedere §10.2.9 e §10.3.7.
16.5.7 Significato di questo
Il significato di this in uno struct è diverso dal significato di this in una classe, come descritto in §12.8.14. Quando un tipo di struct esegue l'override di un metodo virtuale ereditato da System.ValueType (ad esempio Equals, GetHashCodeo ToString), la chiamata del metodo virtuale tramite un'istanza del tipo di struct non causa l'esecuzione del boxing. Questo vale anche quando la struttura viene usata come parametro tipo e la chiamata avviene tramite un'istanza del tipo parametro.
Esempio:
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>(); }L'output del programma è:
1 2 3Anche se è cattivo stile per
ToStringavere effetti collaterali, l'esempio dimostra che non si è verificato alcun boxing per le tre chiamate dix.ToString().esempio finale
Analogamente, la «boxing» non avviene mai implicitamente quando si accede a un membro di un parametro di tipo vincolato, quando tale membro è implementato nel tipo valore. Si supponga, ad esempio, che un'interfaccia ICounter contenga un metodo Increment, che può essere usato per modificare un valore. Se ICounter viene usato come vincolo, l'implementazione del metodo Increment viene chiamata con un riferimento alla variabile su cui Increment è stato chiamato, non viene mai eseguita una copia boxed.
Esempio:
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>(); }La prima chiamata a
Incrementmodifica il valore nella variabilex. Non è equivalente alla seconda chiamata aIncrement, che modifica il valore in una copia incapsulata dix. Di conseguenza, l'output del programma è:0 1 1esempio finale
16.5.8 Inizializzatori di campo
Come descritto in §16.5.5, il valore predefinito di uno struct è costituito dal valore risultante dall'impostazione di tutti i campi di tipo valore sul valore predefinito e su tutti i campi del tipo di riferimento su null. I campi statici e di istanza di uno struct possono includere inizializzatori di variabili; Tuttavia, nel caso di un inizializzatore di campi di istanza, almeno un costruttore di istanza deve essere dichiarato o per uno struct di record, sarà presente un delimited_parameter_list .
Esempio:
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)esempio finale
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 dopo l'immissione al costruttore.
Quando un costruttore di istanza di struct dispone di un this() inizializzatore del costruttore che rappresenta il costruttore senza parametri predefinito, il costruttore dichiarato cancella in modo implicito tutti i campi dell'istanza ed esegue le inizializzazioni specificate dal variable_initializerdei campi dell'istanza dichiarati nello struct. Immediatamente dopo l'ingresso al costruttore, tutti i campi del tipo di 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 alla variable_initializer.
Un field_declaration dichiarato direttamente all'interno di un struct_declaration con il struct_modifierreadonly avrà il field_modifierreadonly.
16.5.9 Costruttori
Uno struct può dichiarare costruttori di istanza, con zero o più parametri. Se uno struct non ha un costruttore di istanza senza parametri dichiarato in modo esplicito, ne viene sintetizzato uno con accessibilità pubblica, che restituisce sempre il valore risultante dall'impostazione di tutti i campi del tipo di valore sul valore predefinito e su tutti i campi del tipo di riferimento a null (§8.3.3). In questo caso, gli inizializzatori di campo dell'istanza vengono ignorati quando viene eseguito il costruttore.
Un costruttore di istanza senza parametri dichiarato in modo esplicito deve avere accessibilità pubblica.
Esempio: dato quanto segue:
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)Le istruzioni creano un
Pointoggetto conxeyinizializzato su zero, che nel caso della chiamata al costruttore dell'istanza senza parametri, possono essere sorprendenti, poiché entrambi i campi dell'istanza hanno inizializzatori, ma non vengono eseguiti.esempio finale
Un costruttore di istanza di struct non è autorizzato a includere un inizializzatore del costruttore del modulo base(argument_list), dove argument_list è facoltativo. L'esecuzione di un costruttore di istanza non comporta l'esecuzione di un costruttore nel tipo System.ValueTypedi base dello struct .
Il this parametro di un costruttore di istanza di struct corrisponde a un parametro di output del tipo struct. Di conseguenza, this sarà attribuito definitivamente (§9.4) in ogni posizione in cui il costruttore restituisce. Analogamente, non può essere letto (anche in modo implicito) nel corpo del costruttore prima di essere assegnato definitivamente.
Se il costruttore dell'istanza dello struct specifica un inizializzatore del costruttore, tale inizializzatore viene considerato un'assegnazione definita a questo che si verifica prima del corpo del costruttore. Pertanto, il corpo stesso non ha requisiti di inizializzazione.
I campi dell'istanza (diversi dai fixed campi) devono essere assegnati in modo definitivo nei costruttori di istanza dello struct che non dispongono di un this() inizializzatore.
Esempio: considerare l'implementazione del costruttore di istanza seguente:
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 } }Nessun membro della funzione di istanza (incluse le funzioni di accesso set per le proprietà
XeY) può essere chiamato fino a quando non vengono assegnati tutti i campi dello struct da costruire. Si noti, tuttavia, che sePointfosse una classe anziché uno struct, l'implementazione del costruttore di istanza sarebbe consentita. C'è un'eccezione a questo e che implica proprietà implementate automaticamente (§15.7.4). Le regole di assegnazione definite (§12.24.2) esentano specificamente l'assegnazione a una proprietà automatica di un tipo struct all'interno di un costruttore di istanza di tale tipo di struct: tale assegnazione è considerata un'assegnazione definita del campo sottostante nascosto della proprietà automatica. Di conseguenza, è consentito quanto segue: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 } }esempio finale]
16.5.10 Costruttori statici
I costruttori statici per gli struct seguono la maggior parte delle stesse regole delle classi. L'esecuzione di un costruttore statico per un tipo di struct viene attivata dal primo degli eventi seguenti che si verificano all'interno di un dominio applicazione:
- Viene fatto riferimento a un membro statico del tipo di struct.
- Viene chiamato un costruttore esplicitamente dichiarato del tipo struct.
Nota: la creazione di valori predefiniti (§16.5.5) di tipi di struct non attiva il costruttore statico. Un esempio è il valore iniziale degli elementi in una matrice. nota finale
16.5.11 Proprietà
Un property_declaration (§15.7.1) per una proprietà di istanza in un struct_declaration può contenere il property_modifierreadonly. Tuttavia, una proprietà statica non deve contenere tale modificatore.
È un errore di compilazione tentare di modificare lo stato di una variabile di istanza di una struct tramite una proprietà di sola lettura dichiarata in tale struct.
Si tratta di un errore in fase di compilazione per una proprietà implementata automaticamente con un readonly modificatore, per avere anche una set funzione di accesso.
Si verifica un errore di compilazione per una proprietà implementata automaticamente in un readonly struct se dispone di un set accessore.
Una proprietà implementata automaticamente dichiarata all'interno di uno readonly struct non deve avere un readonly modificatore, perché si presuppone che la funzione get di accesso sia in modo implicito di sola lettura.
È un errore in fase di compilazione avere un modificatore readonly direttamente su una proprietà, così come su uno qualsiasi dei suoi accessori get e set.
È un errore in fase di compilazione avere un modificatore readonly su tutti gli accessori di una proprietà.
Nota: per correggere l'errore, spostare il modificatore dalle funzioni di accesso alla proprietà stessa. nota finale
Per un'espressione della funzione di accesso alle proprietà, s.P:
- Si tratta di un errore in fase di compilazione se
s.Prichiama la funzioneMdi accesso set di tipoTquando il processo in §12.6.6.1 creerebbe una copia temporanea dis. - Se
s.Prichiama la funzione di accesso get di tipoT, viene seguito il processo in §12.6.6.1 , inclusa la creazione di una copia temporanea disse necessario.
Le proprietà implementate automaticamente (§15.7.4) usano campi sottostanti nascosti, accessibili solo alle funzioni di accesso alle proprietà.
Nota: questa restrizione di accesso indica che i costruttori negli struct contenenti proprietà implementate automaticamente richiedono spesso un inizializzatore di costruttore esplicito in cui non ne sarebbe altrimenti necessario uno, per soddisfare il requisito di tutti i campi assegnati in modo definitivo prima che qualsiasi membro della funzione venga richiamato o il costruttore restituisca. nota finale
Metodi 16.5.12
Un method_declaration (§15.6.1) per un metodo di istanza in un struct_declaration può contenere il method_modifierreadonly. Tuttavia, un metodo statico non deve contenere tale modificatore.
Si tratta di un errore di compilazione per tentare di modificare lo stato di una variabile di struct di istanza tramite un metodo di sola lettura dichiarato nella stessa struct.
Anche se un metodo readonly può chiamare un metodo non readonly a livello di pari, oppure un accessor get di una proprietà o di un indicizzatore, ciò comporta la creazione di una copia implicita di this come misura difensiva.
Un metodo readonly può chiamare una proprietà di pari livello o una funzione di accesso set di indicizzatore di sola lettura. Se l'accessor di un membro di pari livello non è in modalità sola lettura esplicita o implicita, si verifica un errore di compilazione.
Tutti i method_declaration di un metodo parziale devono avere un readonly modificatore, oppure nessuno deve averlo.
16.5.13 Indicizzatori
Un indexer_declaration (§15,9) per un indicizzatore di istanze in un struct_declaration può contenere il indexer_modifierreadonly.
È un errore di compilazione tentare di modificare lo stato di una variabile strutturale di istanza tramite un indicizzatore di sola lettura dichiarato in quella struttura.
Si tratta di un errore di compilazione avere un modificatore readonly su un indicizzatore stesso, così come su ciascuno dei suoi accessor get o set.
È un errore in fase di compilazione se un indicizzatore ha un modificatore readonly su tutti i suoi accessori.
Nota: per correggere l'errore, spostare il modificatore dalle funzioni di accesso all'indicizzatore stesso. nota finale
16.5.14 Eventi
Un event_declaration (§15.8.1) per un evento non di tipo campo in un struct_declaration può contenere il event_modifierreadonly. Tuttavia, un evento statico non deve contenere tale modificatore.
16.5.15 Vincolo di contesto sicuro
16.5.15.1 Generale
In fase di compilazione, ogni espressione è associata a un contesto in cui tale istanza e tutti i relativi campi possono essere accessibili in modo sicuro, il relativo contesto sicuro. Il contesto sicuro è un contesto che racchiude un'espressione in cui è sicuro per il valore essere trasferito.
Qualsiasi espressione il cui tipo in fase di compilazione non è un ref struct ha il contesto sicuro del contesto chiamante.
Un'espressione default , per qualsiasi tipo, ha un contesto sicuro di contesto del chiamante.
Per qualsiasi espressione non predefinita il cui tipo a tempo di compilazione è un ref struct, si ha un contesto sicuro definito dalle sezioni seguenti.
Il contesto sicuro registra il contesto in cui può essere copiato un valore. Data un'assegnazione da un'espressione E1 con un contesto S1sicuro a un'espressione E2 con contesto S2sicuro, si tratta di un errore se S2 è un contesto più ampio di S1.
Esistono tre valori di contesto sicuro diversi, uguali ai valori del contesto di riferimento definiti per le variabili di riferimento (§9.7.2): declaration-block, function-member e caller-context. Il contesto sicuro di un'espressione vincola l'uso come indicato di seguito:
- Per un'istruzione return
return e1, il contesto sicuro die1deve essere il contesto del chiamante. - Per un'assegnazione
e1 = e2il contesto sicuro die2deve essere almeno tanto ampio quanto il contesto sicuro die1.
Per una chiamata al metodo, se è presente un ref o out argomento di un ref struct tipo (incluso il ricevitore, a meno che il tipo non sia readonly), con contesto S1 sicuro, allora nessun argomento (incluso il ricevitore) può avere un contesto sicuro più ristretto di S1.
16.5.15.2 Contesto sicuro dei parametri
Un parametro di un tipo struct ref, incluso il parametro di un metodo d'istanza, ha un contesto sicuro corrispondente al contesto del chiamante.
16.5.15.3 Contesto sicuro delle variabili locali
Una variabile locale di un tipo di struct ref ha un contesto sicuro come indicato di seguito:
- Se la variabile è una variabile di iterazione di un
foreachciclo, il contesto sicuro della variabile corrisponde al contesto sicuro dell'espressioneforeachdel ciclo. - In caso contrario, se la dichiarazione della variabile ha un inizializzatore, il contesto sicuro della variabile corrisponde al contesto sicuro di tale inizializzatore.
- In caso contrario, la variabile non viene inizializzata al punto di dichiarazione e ha un contesto sicuro di contesto del chiamante.
16.5.15.4 Contesto sicuro del campo
Un riferimento a un campo e.F, dove il tipo di F è un tipo di struct ref, ha un contesto sicuro uguale al contesto sicuro di e.
Operatori 16.5.15.5
L'applicazione di un operatore definito dall'utente viene considerata una chiamata al metodo (§16.5.15.6).
Per un operatore che restituisce un valore, ad esempio e1 + e2 o c ? e1 : e2, il contesto sicuro del risultato è il contesto più stretto tra i contesti sicuri degli operandi dell'operatore. Di conseguenza, per un operatore unario che restituisce un valore, ad esempio +e, il contesto sicuro del risultato è il contesto sicuro dell'operando.
Nota: il primo operando di un operatore condizionale è un
bool, quindi il contesto sicuro è il contesto del chiamante. Segue che il contesto sicuro risultante è il contesto sicuro più stretto del secondo e terzo operando. nota finale
16.5.15.6 Metodo e chiamata di proprietà
Un valore risultante da una chiamata al metodo e1.M(e2, ...) o alla chiamata alla proprietà e.P ha un contesto sicuro del più piccolo dei contesti seguenti:
- contesto del chiamante
- Contesto sicuro di tutte le espressioni degli argomenti (incluso il ricevente).
Una chiamata di proprietà ( get o set) viene considerata una chiamata al metodo del metodo sottostante dalle regole precedenti.
16.5.15.7 stackalloc
Il risultato di un'espressione stackalloc ha un contesto sicuro di funzione-membro.
Chiamate al costruttore 16.5.15.8
Un'espressione new che richiama un costruttore rispetta le stesse regole di una chiamata al metodo considerata per restituire il tipo costruito.
Inoltre, il contesto sicuro è il più piccolo dei contesti sicuri di tutti gli argomenti e gli operandi di tutte le espressioni di inizializzatore di oggetti, se è presente un inizializzatore, in modo ricorsivo.
Nota: queste regole si basano sulla
Span<T>mancata presenza di un costruttore del formato seguente:public Span<T>(ref T p)Tale costruttore rende le istanze di
Span<T>usate come campi indistinguibili da un camporef. Le regole di sicurezza descritte in questo documento dipendono dai campirefnon essendo un costrutto valido in C# o .NET. nota finale
ECMA C# draft specification