Struct di record
Nota
Questo articolo è una specifica di 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 riportate nelle note pertinenti della riunione di progettazione linguistica (LDM) .
Altre informazioni sul processo per l'adozione di speclet di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche di .
Problema del campione: https://github.com/dotnet/csharplang/issues/4334
La sintassi per uno struct di record è la seguente:
record_struct_declaration
: attributes? struct_modifier* 'partial'? 'record' 'struct' identifier type_parameter_list?
parameter_list? struct_interfaces? type_parameter_constraints_clause* record_struct_body
;
record_struct_body
: struct_body
| ';'
;
I tipi di strutture di record sono tipi di valore, come altri tipi di struct. Ereditano in modo implicito dalla classe System.ValueType
.
I modificatori e i membri di uno struct di record sono soggetti alle stesse restrizioni di quelle degli struct (accessibilità di tipo, modificatori nei membri, inizializzatori del costruttore di istanze base(...)
, assegnazione definita per this
nel costruttore, distruttori, ...). Gli struct di record seguiranno anche le stesse regole degli struct per i costruttori di istanze senza parametri e gli inizializzatori di campo, ma questo documento presuppone che tali restrizioni verranno rimosse in genere per gli struct.
Vedere §16.4.9 Vedere costruttori di struct senza parametri specifica.
Le struct di record non possono usare il modificatore ref
.
Al massimo una dichiarazione di tipo parziale di un record struct parziale può fornire un parameter_list
.
Il parameter_list
può essere vuoto.
I parametri dello struct di record non possono usare ref
, out
o modificatori di this
(ma sono consentiti in
e params
).
Membri di una struttura record
Oltre ai membri dichiarati nel corpo della struttura del record, un tipo di struttura del record dispone di membri sintetizzati aggiuntivi. I membri vengono sintetizzati a meno che un membro con una firma "corrispondente" non venga dichiarato nel corpo dello struct del record o un membro concreto e accessibile non virtuale con una firma "corrispondente" venga ereditato. Due membri vengono ritenuti corrispondenti se hanno la stessa firma o se, in uno scenario di ereditarietà, sono considerati "nascosti". Vedi Firme e sovraccarico §7.6. Si tratta di un errore se un membro di una struct di record è denominato "Clone".
È un errore che un campo di istanza di una struttura di record abbia un tipo non sicuro.
Una struct di record non è autorizzata a dichiarare un distruttore.
I membri sintetizzati sono i seguenti:
Membri per l'uguaglianza
I membri di uguaglianza sintetizzati sono simili a quelli in una classe di record (Equals
per questo tipo, Equals
per un tipo object
, operatori ==
e !=
per questo tipo),
ad eccezione della mancanza di EqualityContract
, dei controlli nulli né dell'ereditarietà.
Lo struct record implementa System.IEquatable<R>
e include un sovraccarico fortemente tipizzato di Equals(R other)
in cui R
è lo struct record.
Il metodo è public
.
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.
Se Equals(R other)
è definito dall'utente (non sintetizzato) ma non è GetHashCode
, viene generato un avviso.
public readonly bool Equals(R other);
Il Equals(R)
sintetizzato restituisce true
se e solo se per ogni campo di istanza fieldN
nel struct di record il valore di System.Collections.Generic.EqualityComparer<TN>.Default.Equals(fieldN, other.fieldN)
, in cui TN
è il tipo di campo, è true
.
La struct record include operatori ==
e !=
sintetizzati ed 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 metodo Equals
chiamato dall'operatore ==
è il metodo Equals(R other)
specificato in precedenza. L'operatore !=
delega all'operatore ==
. Si tratta di un errore se gli operatori vengono dichiarati in modo esplicito.
La struttura 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 restituisce other is R temp && Equals(temp)
dove R
è la struttura del record.
La struttura di record include un override sintetizzato equivalente a un metodo dichiarato come segue:
public override readonly int GetHashCode();
Il metodo può essere dichiarato in modo esplicito.
Viene segnalato un avviso se una delle Equals(R)
e GetHashCode()
viene dichiarata in modo esplicito, ma l'altro metodo non è esplicito.
L'override sintetizzato di GetHashCode()
restituisce un risultato int
risultante dalla combinazione dei valori di System.Collections.Generic.EqualityComparer<TN>.Default.GetHashCode(fieldN)
per ogni campo di istanza fieldN
, dove TN
è il tipo di fieldN
.
Si consideri, ad esempio, la seguente struttura di record:
record struct R1(T1 P1, T2 P2);
Per questo struct di record, 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 Combine(
EqualityComparer<T1>.Default.GetHashCode(P1),
EqualityComparer<T2>.Default.GetHashCode(P2));
}
}
Membri di stampa: metodi PrintMembers e ToString
La struttura di record include un metodo sintetico equivalente a un metodo dichiarato come segue:
private bool PrintMembers(System.Text.StringBuilder builder);
Il metodo esegue le operazioni seguenti:
- per ciascuno dei membri stampabili dello struct record (campo pubblico non statico o proprietà leggibile), aggiunge il nome del membro seguito da " = " seguito dal valore del membro separato con ", ",
- Restituire true se la struttura del record ha membri stampabili.
Per un membro con un tipo valore, il relativo valore verrà convertito in una rappresentazione di stringa usando il metodo più efficiente disponibile per la piattaforma di destinazione. Attualmente significa chiamare ToString
prima di passare a StringBuilder.Append
.
Se i membri stampabili del record non includono una proprietà leggibile con un accessore nonreadonly
get
, allora il PrintMembers
sintetizzato è readonly
. Non è necessario che i campi del record siano readonly
affinché il metodo PrintMembers
sia readonly
.
Il metodo PrintMembers
può essere dichiarato in modo esplicito.
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 a un metodo dichiarato come segue:
public override string ToString();
Se il metodo PrintMembers
nella struct del record è readonly
, allora il metodo ToString()
sintetizzato è 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.
Metodo sintetizzato:
- crea un'istanza di
StringBuilder
, - aggiunge il nome dello struct di record al generatore, seguito da " { ",
- richiama il metodo di
PrintMembers
dello struct del record assegnandogli il generatore, seguito da " " se ha restituito "vero,". - appends "}",
- restituisce il contenuto del generatore con
builder.ToString()
.
Si consideri, ad esempio, la seguente struttura di record:
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();
}
}
Membri del record struct posizionale
Oltre ai membri precedenti, gli struct di record con un elenco di parametri ("record posizionali") sintetizzano membri aggiuntivi con le stesse condizioni dei membri precedenti.
Costruttore primario
Uno struct di record ha un costruttore pubblico la cui firma corrisponde ai parametri di valore della dichiarazione di tipo. Questo è chiamato il costruttore primario del tipo. È un errore avere un costruttore primario e un costruttore con la stessa firma già presenti nella struttura. Se la dichiarazione di tipo non include un elenco di parametri, non viene generato alcun costruttore primario.
record struct R1
{
public R1() { } // ok
}
record struct R2()
{
public R2() { } // error: 'R2' already defines constructor with same parameter types
}
Le dichiarazioni di campo dell'istanza per un record struct 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 costruttori this
esplicito 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 nell'ambito di visibilità all'interno degli inizializzatori di campi o proprietà dell'istanza. L'uso di membri dell'istanza in queste posizioni risulterebbe in un errore (analogamente a come accade negli inizializzatori di costruttori regolari oggi, dove si verificano errori se utilizzati), ma i parametri del costruttore primario sarebbero nell'ambito e utilizzabili e oscurerebbero i membri dell'istanza. I membri statici sarebbero utilizzabili anche.
Se un parametro del costruttore primario non viene letto, viene generato un avviso.
Le regole di assegnazione definite per i costruttori di istanza delle struct si applicano al costruttore primario delle strutture di record. Ad esempio, di seguito è riportato un errore:
record struct Pos(int X) // definite assignment error in primary constructor
{
private int x;
public int X { get { return x; } set { x = value; } } = X;
}
Proprietà
Per ogni parametro struct di record di una dichiarazione di struct di record esiste un membro della proprietà pubblica corrispondente il cui nome e tipo vengono ricavati dalla dichiarazione del parametro value.
Per una struttura di record:
- Viene creata una proprietà automatica
get
einit
pubblica se lo struct del record include il modificatorereadonly
, altrimentiget
eset
. Entrambi i tipi di funzioni di accesso set (set
einit
) sono considerati "corrispondenti". L'utente può quindi dichiarare una proprietà init-only al posto di una modificabile sintetizzata. Viene eseguito l'override di una proprietàabstract
ereditata con 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 di funzioni di accessopublic
get
eset
/init
. Si tratta di un errore se la proprietà o il campo ereditato è nascosto.
La proprietà automatica viene inizializzata al valore del parametro del costruttore primario corrispondente. Gli attributi possono essere applicati alla proprietà automatica sintetizzata e al relativo campo sottostante utilizzando i targetproperty:
ofield:
come target per gli attributi sintatticamente applicati al parametro corrispondente della struct di record.
Decostruire
Uno struct di record posizionale con almeno un parametro genera automaticamente un metodo di istanza pubblico a cui il ritorno è void, denominato Deconstruct
, comprendente una dichiarazione di parametro out per ogni parametro della dichiarazione del costruttore primario. Ogni parametro del metodo Deconstruct ha lo stesso tipo del parametro corrispondente della dichiarazione del costruttore primario. Il corpo del metodo assegna a ogni parametro del metodo Deconstruct il valore ottenuto dall'accesso a un membro dell'istanza con lo stesso nome.
Se i membri dell'istanza a cui si accede nel corpo del metodo non includono una proprietà con un accessor non-readonly
get
, allora il metodo Deconstruct
sintetizzato è 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.
Consenti l'espressione with
sulle strutture
È ora valido per un ricevitore avere un tipo struttura in un'espressione with
.
Sulla destra dell'espressione with
è presente un member_initializer_list
con una sequenza di assegnazioni all'identificatore di, che deve essere un campo o una proprietà di istanza accessibile di tipo ricevitore.
Per un ricevitore con tipo struct, il ricevitore viene prima copiato, quindi ogni member_initializer
viene elaborato allo stesso modo di un'assegnazione a un campo o a un accesso alle proprietà del risultato della conversione.
Le assegnazioni vengono elaborate in ordine lessicale.
Miglioramenti dei record
Consenti record class
La sintassi esistente per i tipi di record consente di record class
con lo stesso significato di record
:
record_declaration
: attributes? class_modifier* 'partial'? 'record' 'class'? identifier type_parameter_list?
parameter_list? record_base? type_parameter_constraints_clause* record_body
;
Consenti ai membri posizionaali definiti dall'utente di essere campi
Fare riferimento a https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-10-05.md#changing-the-member-type-of-a-primary-constructor-parameter
Non viene creata alcuna proprietà automatica se il record ha o eredita un campo dell'istanza con il nome e il tipo previsti.
Consenti costruttori senza parametri e inizializzatori di membri negli struct
Fare riferimento alla specifica dei costruttori di struct senza parametri .
Domande aperte
- come riconoscere le strutture di record nei metadati? (non abbiamo un metodo clone ineffabile da sfruttare...)
Risposto
- confermare che si vuole mantenere la progettazione PrintMembers (metodo separato che restituisce
bool
) (risposta: sì) - confermare che non permetteremo
record ref struct
(problema conIEquatable<RefStruct>
e i campi ref) (risposta: sì) - confermare l'implementazione dei membri di uguaglianza. In alternativa, gli operatori
bool Equals(R other)
,bool Equals(object? other)
e quelli sintetizzati delegano tutti aValueType.Equals
. (risposta: sì) - confermare che si vogliono consentire gli inizializzatori di campo quando è presente un costruttore primario. Vogliamo anche consentire costruttori di struct senza parametri mentre ci siamo (dato che il problema di Activator è stato apparentemente risolto)? (risposta: sì, la specifica aggiornata deve essere esaminata in LDM)
- quanto vogliamo dire sul metodo
Combine
? (risposta: il meno possibile) - Dovremmo disabilitare un costruttore definito dall'utente con una firma di costruttore di copia? (risposta: no, non esiste alcuna nozione di costruttore di copia nella specifica delle strutture di record)
- confermare che vogliamo impedire i membri denominati "Clone". (risposta: risposta esatta)
- verificare che la logica di
Equals
sintetizzata sia funzionalmente equivalente all'implementazione di runtime ,ad esempio float. NaN) (risposta: confermato in LDM) - Gli attributi di destinazione sia dei campi che delle proprietà possono essere inseriti nell'elenco dei parametri posizionali? (risposta: sì, uguale a per la classe record)
-
with
sui generici? (risposta: fuori ambito per C# 10) - Dovrebbe
GetHashCode
includere un hash del tipo stesso per ottenere valori diversi trarecord struct S1;
erecord struct S2;
? (risposta: no)
C# feature specifications