Record (riferimenti per C#)
Si usa il modificatore record
per definire un tipo di riferimento che fornisce funzionalità predefinite per incapsulare i dati. C# 10 consente alla sintassi record class
come sinonimo di chiarire un tipo riferimento e record struct
di definire un tipo di valore con funzionalità simili.
Quando si dichiara un costruttore primario in un record, il compilatore genera proprietà pubbliche per i parametri del costruttore primario. I parametri del costruttore primario per un record vengono definiti parametri posizionali. Il compilatore crea proprietà posizionali che rispecchiano il costruttore primario o i parametri posizionali. Il compilatore non esegue la sintesi delle proprietà per i parametri del costruttore primario nei tipi che non dispongono del modificatore record
.
I due esempi seguenti illustrano i tipi di riferimento record
(o record class
):
public record Person(string FirstName, string LastName);
public record Person
{
public required string FirstName { get; init; }
public required string LastName { get; init; }
};
I due esempi seguenti illustrano i tipi di valore record struct
:
public readonly record struct Point(double X, double Y, double Z);
public record struct Point
{
public double X { get; init; }
public double Y { get; init; }
public double Z { get; init; }
}
È inoltre possibile creare record con proprietà e campi modificabili:
public record Person
{
public required string FirstName { get; set; }
public required string LastName { get; set; }
};
Gli struct di record possono anche essere modificabili. Lo sono sia gli struct di record posizionali che gli struct di record senza parametri posizionali:
public record struct DataMeasurement(DateTime TakenAt, double Measurement);
public record struct Point
{
public double X { get; set; }
public double Y { get; set; }
public double Z { get; set; }
}
Anche se i record possono essere modificabili, sono destinati principalmente al supporto di modelli di dati non modificabili. Il tipo di record offre le seguenti funzionalità:
- Sintassi concisa per la creazione di un tipo riferimento con proprietà non modificabili
- Comportamento predefinito utile per un tipo di riferimento incentrato sui dati:
- Supporto per le gerarchie di ereditarietà
Gli esempi precedenti mostrano alcune distinzioni tra record che sono tipi di riferimento e record che sono tipi valore:
- Un oggetto
record
orecord class
dichiara un tipo riferimento. La parola chiaveclass
è facoltativa, ma può aggiungere chiarezza per i lettori. Un oggettorecord struct
dichiara un tipo di valore. - Le proprietà posizionali non sono modificabili in un oggetto
record class
e in un oggettoreadonly record struct
. Sono modificabili in un oggettorecord struct
.
Nella parte restante di questo articolo vengono illustrati sia i tipi record class
che record struct
. Le differenze sono descritte in dettaglio in ogni sezione. È consigliabile scegliere tra un oggetto record class
e un oggetto record struct
simile alla scelta tra un oggetto class
e un oggetto struct
. Il termine record viene usato per descrivere il comportamento applicabile a tutti i tipi di record. Viene usato record struct
o record class
per descrivere il comportamento che si applica rispettivamente ai tipi di struct o di classe. Il tipo record struct
è stato introdotto in C# 10.
Sintassi posizionale per la definizione di proprietà
È possibile usare i parametri posizionali per dichiarare le proprietà di un record e inizializzare i valori delle proprietà quando si crea un'istanza:
public record Person(string FirstName, string LastName);
public static void Main()
{
Person person = new("Nancy", "Davolio");
Console.WriteLine(person);
// output: Person { FirstName = Nancy, LastName = Davolio }
}
Quando si usa la sintassi posizionale per la definizione di proprietà, il compilatore crea:
- Proprietà pubblica di impostazione automatica per ogni parametro posizionale fornito nella dichiarazione di record.
- Per tipi
record
ereadonly record struct
tipi: una proprietà init-only. - Per i tipi
record struct
: proprietà di lettura/scrittura.
- Per tipi
- Costruttore primario i cui parametri corrispondono ai parametri posizionali nella dichiarazione di record.
- Per i tipi di struct di record, un costruttore senza parametri che imposta ogni campo sul valore predefinito.
- Un Metodo
Deconstruct
con un parametroout
per ogni parametro posizionale fornito nella dichiarazione di record. Il metodo decostruisce le proprietà definite utilizzando la sintassi posizionale; ignora le proprietà definite utilizzando la sintassi della proprietà standard.
È possibile aggiungere attributi a uno di questi elementi creati dal compilatore dalla definizione del record. È possibile aggiungere un database di destinazione a qualsiasi attributo applicato alle proprietà del record posizionale. Il seguente esempio viene applicato a ogni proprietà System.Text.Json.Serialization.JsonPropertyNameAttribute del record Person
. La destinazione property:
indica che l'attributo viene applicato alla proprietà generata dal compilatore. Altri valori sono field:
per applicare l'attributo al campo e param:
per applicare l'attributo al parametro.
/// <summary>
/// Person record type
/// </summary>
/// <param name="FirstName">First Name</param>
/// <param name="LastName">Last Name</param>
/// <remarks>
/// The person type is a positional record containing the
/// properties for the first and last name. Those properties
/// map to the JSON elements "firstName" and "lastName" when
/// serialized or deserialized.
/// </remarks>
public record Person([property: JsonPropertyName("firstName")] string FirstName,
[property: JsonPropertyName("lastName")] string LastName);
Nell'esempio precedente viene inoltre illustrato come creare commenti in formato documentazione XML per il record. È possibile aggiungere il tag <param>
per aggiungere la documentazione per i parametri del costruttore primario.
Se la definizione della proprietà implementata automaticamente generata non è quella desiderata, è possibile definire la propria proprietà con lo stesso nome. Ad esempio, è possibile modificare l'accessibilità o la mutabilità oppure fornire un'implementazione per la funzione di accesso get
o set
. Se si dichiara la proprietà nell'origine, è necessario inizializzarla dal parametro posizionale del record. Se la proprietà è una proprietà implementata automaticamente, è necessario inizializzare la proprietà. Se si aggiunge un campo sottostante nell'origine, è necessario inizializzare il campo sottostante. Il decostruttore generato usa la definizione della proprietà. L'esempio seguente, ad esempio, dichiara le proprietà FirstName
e LastName
di un record posizionale public
, ma limita il parametro posizionale Id
a internal
. È possibile usare questa sintassi per i record e i tipi di struct di record.
public record Person(string FirstName, string LastName, string Id)
{
internal string Id { get; init; } = Id;
}
public static void Main()
{
Person person = new("Nancy", "Davolio", "12345");
Console.WriteLine(person.FirstName); //output: Nancy
}
Un tipo di record non deve dichiarare alcuna proprietà posizionale. È possibile dichiarare un record senza proprietà posizionale ed è possibile dichiarare altri campi e proprietà, come nell'esempio seguente:
public record Person(string FirstName, string LastName)
{
public string[] PhoneNumbers { get; init; } = [];
};
Se si definiscono le proprietà usando la sintassi della proprietà standard ma si omette il modificatore di accesso, le proprietà sono implicitamente private
.
Immutabilità
Un record posizionale e uno struct di record posizionale di sola lettura dichiarano le proprietà init-only. Uno struct di record posizionale dichiara le proprietà di lettura/scrittura. È possibile eseguire l'override di una di queste impostazioni predefinite, come illustrato nella sezione precedente.
L'immutabilità può essere utile quando è necessario un tipo incentrato sui dati thread-safe o a seconda di un codice hash che rimane invariato in una tabella hash. L'immutabilità non è tuttavia appropriata per tutti gli scenari di dati. Entity Framework Core, ad esempio, non supporta l'aggiornamento con tipi di entità non modificabili.
Le proprietà init-only, indipendentemente dal fatto che siano state create da parametri posizionali (record class
e readonly record struct
) o specificando le funzioni di accesso init
, hanno immutabilità superficiale. Dopo l'inizializzazione, non è possibile modificare il valore delle proprietà di tipo valore o il riferimento delle proprietà di tipo riferimento. Tuttavia, è possibile modificare i dati a cui fa riferimento una proprietà di tipo riferimento. L'esempio seguente mostra che il contenuto di una proprietà non modificabile di tipo riferimento (in questo caso una matrice) è modificabile:
public record Person(string FirstName, string LastName, string[] PhoneNumbers);
public static void Main()
{
Person person = new("Nancy", "Davolio", new string[1] { "555-1234" });
Console.WriteLine(person.PhoneNumbers[0]); // output: 555-1234
person.PhoneNumbers[0] = "555-6789";
Console.WriteLine(person.PhoneNumbers[0]); // output: 555-6789
}
Le funzionalità univoche per i tipi di record vengono implementate dai metodi sintetizzati dal compilatore e nessuno di questi metodi compromette l'immutabilità modificando lo stato dell'oggetto. Se non specificato, i metodi sintetizzati vengono generati per le dichiarazioni di record
, record struct
e readonly record struct
.
Uguaglianza di valori
Se non si esegue l'override o si sostituiscono metodi di uguaglianza, il tipo dichiarato determina la modalità di definizione dell'uguaglianza:
- Per i tipi
class
, due oggetti sono uguali se fanno riferimento allo stesso oggetto in memoria. - Per i tipi
struct
, due oggetti sono uguali se sono dello stesso tipo e archiviano gli stessi valori. - Per i tipi con il modificatore
record
(record class
,record struct
ereadonly record struct
), due oggetti sono uguali se sono dello stesso tipo e archiviano gli stessi valori.
La definizione di uguaglianza per un oggetto record struct
è identica a quella di un oggetto struct
. La differenza è che per un oggetto struct
, l'implementazione è in ValueType.Equals(Object) e si basa sulla reflection. Per i record, l'implementazione viene sintetizzata dal compilatore e usa i membri dati dichiarati.
L'uguaglianza dei riferimenti è necessaria per alcuni modelli di dati. Ad esempio, Entity Framework Core dipende dall'uguaglianza dei riferimenti per assicurarsi che usi una sola istanza di un tipo di entità per ciò che è concettualmente un'entità. Per questo motivo, i record e gli struct di record non sono appropriati per l'uso come tipi di entità in Entity Framework Core.
L'esempio seguente illustra l'uguaglianza dei valori dei tipi di record:
public record Person(string FirstName, string LastName, string[] PhoneNumbers);
public static void Main()
{
var phoneNumbers = new string[2];
Person person1 = new("Nancy", "Davolio", phoneNumbers);
Person person2 = new("Nancy", "Davolio", phoneNumbers);
Console.WriteLine(person1 == person2); // output: True
person1.PhoneNumbers[0] = "555-1234";
Console.WriteLine(person1 == person2); // output: True
Console.WriteLine(ReferenceEquals(person1, person2)); // output: False
}
Per implementare l'uguaglianza dei valori, il compilatore sintetizza diversi metodi, tra cui:
Override di Object.Equals(Object). Si tratta di un errore se l'override viene dichiarato in modo esplicito.
Questo metodo viene usato come base per il metodo statico Object.Equals(Object, Object) quando entrambi i parametri sono non null.
Un oggetto
virtual
, osealed
,Equals(R? other)
doveR
è il tipo di record. Questo metodo implementa IEquatable<T>. Questo metodo può essere dichiarato in modo esplicito.Se il tipo di record è derivato da un tipo di record di base
Base
,Equals(Base? other)
. Si tratta di un errore se l'override viene dichiarato in modo esplicito. Se si fornisce la propria implementazione diEquals(R? other)
, è necessario fornire anche un'implementazione diGetHashCode
.Override di Object.GetHashCode(). Questo metodo può essere dichiarato in modo esplicito.
Override degli operatori
==
e!=
. Si tratta di un errore se gli operatori vengono dichiarati in modo esplicito.Se il tipo di record è derivato da un tipo di record di base,
protected override Type EqualityContract { get; };
. Questa proprietà può essere dichiarata in modo esplicito. Per ulteriori informazioni, consultare Uguaglianza nelle gerarchie di ereditarietà.
Il compilatore non esegue la sintesi di un metodo quando un tipo di record ha un metodo che corrisponde alla firma di un metodo sintetizzato che può essere dichiarato in modo esplicito.
Mutazione non distruttiva
Se è necessario copiare un'istanza con alcune modifiche, è possibile usare un'espressione with
per ottenere una mutazione non distruttiva. Un'espressione with
crea una nuova istanza di record che è una copia di un'istanza di record esistente, con le proprietà e i campi specificati modificati. Usare la sintassi dell'inizializzatore di oggetto per specificare i valori da modificare, come illustrato nell'esempio seguente:
public record Person(string FirstName, string LastName)
{
public string[] PhoneNumbers { get; init; }
}
public static void Main()
{
Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] };
Console.WriteLine(person1);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
Person person2 = person1 with { FirstName = "John" };
Console.WriteLine(person2);
// output: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { PhoneNumbers = new string[1] };
Console.WriteLine(person2);
// output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] }
Console.WriteLine(person1 == person2); // output: False
person2 = person1 with { };
Console.WriteLine(person1 == person2); // output: True
}
L'espressione with
può impostare proprietà posizionale o proprietà create usando la sintassi della proprietà standard. Le proprietà dichiarate in modo esplicito devono avere una funzione di accesso init
o set
da modificare in un'espressione with
.
Il risultato di un'espressione with
è una copia superficiale, ovvero per una proprietà di riferimento, viene copiato solo il riferimento a un'istanza. Sia il record originale che la copia finiscono con un riferimento alla stessa istanza.
Per implementare questa funzionalità per i tipi record class
, il compilatore sintetizza un metodo clone e un costruttore di copia. Il metodo clone virtuale restituisce un nuovo record inizializzato dal costruttore di copia. Quando si usa un'espressione with
, il compilatore crea codice che chiama il metodo clone e quindi imposta le proprietà specificate nell'espressione with
.
Se è necessario un comportamento di copia diverso, è possibile scrivere il proprio costruttore di copia in un oggetto record class
. In questo caso, il compilatore non ne esegue la sintesi. Impostare il costruttore private
se il record è sealed
; in caso contrario, impostarlo su protected
. Il compilatore non sintetizza un costruttore di copia per i tipi record struct
. È possibile scriverne uno, ma il compilatore non genera chiamate per le espressioni with
. I valori dell’oggetto record struct
vengono copiati in fase di assegnazione.
Non è possibile eseguire l'override del metodo clone e non è possibile creare un membro denominato Clone
in alcun tipo di record. Il nome effettivo del metodo clone viene generato dal compilatore.
Formattazione predefinita per la visualizzazione
I tipi di record hanno un metodo ToString generato dal compilatore che visualizza i nomi e i valori delle proprietà e dei campi pubblici. Il metodo ToString
restituisce una stringa del formato seguente:
<nome tipo di record> {<nome proprietà> = <valore>, <nome proprietà> = <valore>, ...}
La stringa stampata per <value>
è la stringa restituita da ToString() per il tipo della proprietà. Nell'esempio seguente ChildNames
è un oggetto System.Array, dove ToString
restituisce System.String[]
:
Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }
Per implementare questa funzionalità, nei tipi record class
il compilatore sintetizza un metodo virtuale PrintMembers
e un override ToString. Nei tipi record struct
questo membro è private
.
L'override ToString
crea un oggetto StringBuilder con il nome del tipo seguito da una parentesi aperta. Chiama l’oggetto PrintMembers
per aggiungere i nomi e i valori delle proprietà, quindi aggiunge la parentesi quadra chiusa. Il seguente esempio mostra il codice simile al contenuto dell'override sintetizzato:
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("Teacher"); // type name
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
{
stringBuilder.Append(" ");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}
È possibile fornire un'implementazione personalizzata di PrintMembers
o dell'override ToString
. Gli esempi sono disponibili nella sezione PrintMembers
formattazione dei record derivati più avanti in questo articolo. In C# 10 e versioni successive l'implementazione dell’oggetto ToString
può includere il modificatore sealed
, che impedisce al compilatore di sintetizzare un'implementazione ToString
per tutti i record derivati. È possibile creare una rappresentazione di stringa coerente in una gerarchia di tipi record
. (I record derivati hanno ancora un metodo PrintMembers
generato per tutte le proprietà derivate.)
Ereditarietà
Questa sezione si applica solo ai tipi record class
.
Un record può ereditare da un altro record. Tuttavia, un record non può ereditare da una classe e una classe non può ereditare da un record.
Parametri posizionali nei tipi di record derivati
Il record derivato dichiara parametri posizionali per tutti i parametri nel costruttore primario del record di base. Il record di base dichiara e inizializza tali proprietà. Il record derivato non li nasconde, ma crea e inizializza solo le proprietà per i parametri che non sono dichiarati nel record di base.
Nell'esempio seguente viene illustrata l'ereditarietà con la sintassi della proprietà posizionale:
public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public static void Main()
{
Person teacher = new Teacher("Nancy", "Davolio", 3);
Console.WriteLine(teacher);
// output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}
Uguaglianza nelle gerarchie di ereditarietà
Questa sezione si applica ai tipi record class
, ma non ai tipi record struct
. Affinché due variabili di record siano uguali, il tipo di runtime deve essere uguale. I tipi delle variabili contenenti potrebbero essere diversi. Il confronto di uguaglianza ereditato è illustrato nell'esempio di codice seguente:
public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public static void Main()
{
Person teacher = new Teacher("Nancy", "Davolio", 3);
Person student = new Student("Nancy", "Davolio", 3);
Console.WriteLine(teacher == student); // output: False
Student student2 = new Student("Nancy", "Davolio", 3);
Console.WriteLine(student2 == student); // output: True
}
Nell'esempio tutte le variabili vengono dichiarate come Person
, anche quando l'istanza è un tipo derivato di Student
o Teacher
. Le istanze hanno le stesse proprietà e gli stessi valori delle proprietà. Ma l’oggetto student == teacher
restituisce False
anche se entrambe sono variabili di tipo Person
, e student == student2
restituisce True
anche se una è una variabile Person
e una è una variabile Student
. Il test di uguaglianza dipende dal tipo di runtime dell'oggetto effettivo, non dal tipo dichiarato della variabile.
Per implementare questo comportamento, il compilatore sintetizza una proprietà EqualityContract
che restituisce un oggetto Type che corrisponde al tipo del record. L’oggetto EqualityContract
consente ai metodi di uguaglianza di confrontare il tipo di runtime di oggetti quando controllano l'uguaglianza. Se il tipo di base di un record è object
, questa proprietà è virtual
. Se il tipo di base è un altro tipo di record, questa proprietà è un override. Se il tipo di record è sealed
, questa proprietà è effettivamente sealed
perché il tipo è sealed
.
Quando il codice confronta due istanze di un tipo derivato, i metodi di uguaglianza sintetizzati controllano tutti i membri dati dei tipi di base e derivati per verificarne l'uguaglianza. Il metodo sintetizzato GetHashCode
usa il metodo GetHashCode
di tutti i membri dati dichiarati nel tipo di base e nel tipo di record derivato. I membri dati di un oggetto record
includono tutti i campi dichiarati e il campo sottostante sintetizzato dal compilatore per tutte le proprietà di impostazione automatica.
with
espressioni nei record derivati
Il risultato di un'espressione with
ha lo stesso tipo di runtime dell'operando dell'espressione. Tutte le proprietà del tipo di runtime vengono copiate, ma è possibile impostare solo le proprietà del tipo di compilazione, come illustrato nell'esempio seguente:
public record Point(int X, int Y)
{
public int Zbase { get; set; }
};
public record NamedPoint(string Name, int X, int Y) : Point(X, Y)
{
public int Zderived { get; set; }
};
public static void Main()
{
Point p1 = new NamedPoint("A", 1, 2) { Zbase = 3, Zderived = 4 };
Point p2 = p1 with { X = 5, Y = 6, Zbase = 7 }; // Can't set Name or Zderived
Console.WriteLine(p2 is NamedPoint); // output: True
Console.WriteLine(p2);
// output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = A, Zderived = 4 }
Point p3 = (NamedPoint)p1 with { Name = "B", X = 5, Y = 6, Zbase = 7, Zderived = 8 };
Console.WriteLine(p3);
// output: NamedPoint { X = 5, Y = 6, Zbase = 7, Name = B, Zderived = 8 }
}
PrintMembers
formattazione nei record derivati
Il metodo sintetizzato PrintMembers
di un tipo di record derivato chiama l'implementazione di base. Il risultato è che tutti i campi e le proprietà pubbliche dei tipi derivati e di base sono inclusi nell'output, come illustrato nell'esempio ToString
seguente:
public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public static void Main()
{
Person teacher = new Teacher("Nancy", "Davolio", 3);
Console.WriteLine(teacher);
// output: Teacher { FirstName = Nancy, LastName = Davolio, Grade = 3 }
}
È possibile fornire la propria implementazione del metodo PrintMembers
. In questo caso, usare la seguente firma:
- Per un record
sealed
che deriva daobject
(non dichiara un record di base):private bool PrintMembers(StringBuilder builder)
; - Per un record
sealed
che deriva da un altro record (tenere presente che il tipo di inclusione èsealed
, quindi il metodo è effettivamentesealed
):protected override bool PrintMembers(StringBuilder builder)
; - Per un record che non è
sealed
e deriva dall'oggetto:protected virtual bool PrintMembers(StringBuilder builder);
- Per un record che non è
sealed
e deriva da un altro record:protected override bool PrintMembers(StringBuilder builder);
Di seguito è riportato un esempio di codice che sostituisce i metodi sintetizzati PrintMembers
, uno per un tipo di record che deriva dall'oggetto e uno per un tipo di record che deriva da un altro record:
public abstract record Person(string FirstName, string LastName, string[] PhoneNumbers)
{
protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
stringBuilder.Append($"FirstName = {FirstName}, LastName = {LastName}, ");
stringBuilder.Append($"PhoneNumber1 = {PhoneNumbers[0]}, PhoneNumber2 = {PhoneNumbers[1]}");
return true;
}
}
public record Teacher(string FirstName, string LastName, string[] PhoneNumbers, int Grade)
: Person(FirstName, LastName, PhoneNumbers)
{
protected override bool PrintMembers(StringBuilder stringBuilder)
{
if (base.PrintMembers(stringBuilder))
{
stringBuilder.Append(", ");
};
stringBuilder.Append($"Grade = {Grade}");
return true;
}
};
public static void Main()
{
Person teacher = new Teacher("Nancy", "Davolio", new string[2] { "555-1234", "555-6789" }, 3);
Console.WriteLine(teacher);
// output: Teacher { FirstName = Nancy, LastName = Davolio, PhoneNumber1 = 555-1234, PhoneNumber2 = 555-6789, Grade = 3 }
}
Nota
In C# 10 e versioni successive il compilatore esegue la sintesi PrintMembers
nei record derivati anche quando un record di base ha bloccato il metodo ToString
. È inoltre possibile creare un'implementazione personalizzata di PrintMembers
.
Comportamento del decostruttore nei record derivati
Il metodo Deconstruct
di un record derivato restituisce i valori di tutte le proprietà posizionali del tipo in fase di compilazione. Se il tipo di variabile è un record di base, vengono decostruite solo le proprietà del record di base, a meno che l'oggetto non venga eseguito il cast al tipo derivato. Nell'esempio seguente viene illustrata la chiamata di un decostruttore in un record derivato.
public abstract record Person(string FirstName, string LastName);
public record Teacher(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public record Student(string FirstName, string LastName, int Grade)
: Person(FirstName, LastName);
public static void Main()
{
Person teacher = new Teacher("Nancy", "Davolio", 3);
var (firstName, lastName) = teacher; // Doesn't deconstruct Grade
Console.WriteLine($"{firstName}, {lastName}");// output: Nancy, Davolio
var (fName, lName, grade) = (Teacher)teacher;
Console.WriteLine($"{fName}, {lName}, {grade}");// output: Nancy, Davolio, 3
}
Vincoli generici
La parola chiave record
è un modificatore per un tipo class
o struct
. L'aggiunta del modificatore record
include il comportamento descritto in precedenza in questo articolo. Non esiste alcun vincolo generico che richiede che un tipo sia un record. Un oggetto record class
soddisfa il vincolo class
. Un oggetto record struct
soddisfa il vincolo struct
. Per ulteriori informazioni, consultare Vincoli sui parametri di tipo.
Specifiche del linguaggio C#
Per ulteriori informazioni, consultare la sezione Classi della specifica del linguaggio C#.
Per ulteriori informazioni su queste funzionalità, consultare le note sulla proposta di funzionalità seguenti:
Vedi anche
Commenti e suggerimenti
https://aka.ms/ContentUserFeedback.
Presto disponibile: Nel corso del 2024 verranno gradualmente disattivati i problemi di GitHub come meccanismo di feedback per il contenuto e ciò verrà sostituito con un nuovo sistema di feedback. Per altre informazioni, vedereInvia e visualizza il feedback per