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à:

Gli esempi precedenti mostrano alcune distinzioni tra record che sono tipi di riferimento e record che sono tipi valore:

  • Un oggetto record o record class dichiara un tipo riferimento. La parola chiave class è facoltativa, ma può aggiungere chiarezza per i lettori. Un oggetto record struct dichiara un tipo di valore.
  • Le proprietà posizionali non sono modificabili in un oggetto record class e in un oggetto readonly record struct. Sono modificabili in un oggetto record 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 e readonly record struct tipi: una proprietà init-only.
    • Per i tipi record struct: proprietà di lettura/scrittura.
  • 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 parametro out 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 e readonly 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, o sealed, Equals(R? other) dove R è 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 di Equals(R? other), è necessario fornire anche un'implementazione di GetHashCode.

  • 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 da object (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 è effettivamente sealed): 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