Condividi tramite


Record (riferimenti per C#)

Il record modificatore fornisce funzionalità predefinite per incapsulare i dati. La record class sintassi e record definiscono i tipi di riferimento. La record struct sintassi definisce un tipo di valore.

Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle anteprime pubbliche per la versione futura del linguaggio.

La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.

Suggerimento

Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.

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 sono 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. Scegliere tra un record class oggetto e un record struct oggetto simile alla scelta tra un class oggetto e un oggetto struct. Il termine record descrive il comportamento che si applica 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.

Sintassi posizionale per la definizione di proprietà e campo

Utilizzare i parametri posizionali per dichiarare le proprietà di un record o per inizializzare i valori di proprietà o di campo. Nell'esempio seguente viene creato un record con due proprietà posizionale:

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:

  • Una proprietà pubblica implementata automaticamente 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 di proprietà implementata automaticamente generata non è quella desiderata, definire la propria proprietà o campo 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 dichiari il membro nel tuo codice sorgente, devi inizializzarlo utilizzando il 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 de-costruttore generato utilizza la definizione della tua proprietà o campo. 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

}

Se si vuole creare un campo anziché una proprietà, assegnare il parametro posizionale a un campo, come illustrato nell'esempio seguente:

public record Person(string FirstName, string LastName, string Id)
{
    internal readonly string Id = Id; // this.Id set to parameter 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; } = [];
};

Le proprietà generate dal compilatore dai parametri posizionali sono public. I modificatori di accesso vengono dichiarati in modo esplicito in tutte le proprietà dichiarate in modo esplicito.

Immutabilità

Una classe di record posizionale e uno struct posizionale di sola lettura dichiarano proprietà inizializzabili solo. 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 per essere thread-safe o quando si dipende da un codice hash che rimane invariato in una tabella hash. Tuttavia, l'immutabilità non è appropriata per tutti gli scenari di dati. Entity Framework Core, ad esempio, non supporta l'aggiornamento con tipi di entità non modificabili.

Le proprietà solo init, create da parametri posizionali (record class e readonly record struct) o specificando init le funzioni di accesso, hanno un'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 si dichiara l'override 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>. È possibile dichiarare questo metodo 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 si dichiara l'override in modo esplicito. Se si fornisce la propria implementazione di Equals(R? other), è necessario fornire anche un'implementazione di GetHashCode.

  • Override di Object.GetHashCode(). È possibile dichiarare questo metodo in modo esplicito.

  • Override dell'operatore == e dell'operatore !=. Si tratta di un errore se si dichiarano gli operatori in modo esplicito.

  • Se il tipo di record è derivato da un tipo di record di base, protected override Type EqualityContract { get; };. È possibile dichiarare questa proprietà 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 e il metodo può essere dichiarato in modo esplicito.

Mutazione non distruttiva

Se è necessario copiare un'istanza con alcune modifiche, usare un'espressione per ottenere una withmutazione non distruttiva. Un'espressione with crea una nuova istanza di record che rappresenta una copia di un'istanza di record esistente, ma con le proprietà e i campi specificati modificati. Usare la sintassi dell'inizializzatore di oggetti 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. Per una proprietà di riferimento, l'espressione copia solo il riferimento a un'istanza di . 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à incluse nell'espressione with .

Importante

Il compilatore sintetizza anche un costruttore pubblico senza parametri quando il record non dispone di un costruttore primario o di qualsiasi costruttore definito dall'utente. Questo costruttore senza parametri inizializza tutti i campi nei valori predefiniti. Senza questo costruttore sintetizzato, non è disponibile alcun costruttore pubblico.

Se è necessario un comportamento di copia diverso, scrivere il proprio costruttore di copia in un oggetto record class. In tal 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.

Importante

Negli esempi precedenti tutte le proprietà sono indipendenti. Nessuna delle proprietà viene calcolata da altri valori di proprietà. Un'espressione with copia prima l'istanza di record esistente, quindi modifica le proprietà o i campi inclusi nell'espressione with . Le proprietà calcolate nei record tipi devono essere calcolate in base all'accesso, non inizializzate al momento della creazione dell'istanza. In caso contrario, una proprietà potrebbe restituire il valore calcolato in base all'istanza originale, non alla copia modificata.

È possibile garantire la correttezza delle proprietà calcolate calcolando il valore all'accesso, come illustrato nella dichiarazione seguente:

public record Point(int X, int Y)
{
    public double Distance => Math.Sqrt(X * X + Y * Y);
}

Il tipo di record precedente calcola quando Distance si accede, come illustrato nell'esempio seguente:

Point p1 = new Point(3, 4);
Console.WriteLine($"Original point: {p1}");
p1 = p1 with { Y = 8 };
Console.WriteLine($"Modified point: {p1}");
// Output:
// Original point: Point { X = 3, Y = 4, Distance = 5 }
// Modified point: Point { X = 3, Y = 8, Distance = 8.54400374531753 }

A differenza di questo approccio con la dichiarazione seguente, in cui la Distance proprietà viene calcolata e memorizzata nella cache come parte dell'inizializzazione di una nuova istanza:

public record PointInit(int X, int Y)
{
    public double Distance { get; } = Math.Sqrt(X * X + Y * Y);
}

Poiché Distance viene calcolato come parte dell'inizializzazione, il valore viene calcolato e memorizzato nella cache prima che l'espressione with cambi il valore di Y nella copia. Il risultato è che la distanza non è corretta:

PointInit pt1 = new PointInit(3, 4);
Console.WriteLine($"Original point: {pt1}");
pt1 = pt1 with { Y = 8 };
Console.WriteLine($"Incorrect Modified point: {pt1}");
// Output:
// Original point: PointInit { X = 3, Y = 4, Distance = 5 }
// Modified point: PointInit { X = 3, Y = 8, Distance = 5 }

Il Distance calcolo non è costoso da calcolare per ogni accesso. Tuttavia, alcune proprietà calcolate potrebbero richiedere l'accesso a più dati o a calcoli più estesi. In questi casi, anziché un record, usare un class tipo e calcolare il valore memorizzato nella cache quando uno dei componenti cambia valore.

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 ToString metodo restituisce una stringa nel 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. L'implementazione di ToString potrebbe 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 record includono tutti i campi dichiarati e il campo sottostante sintetizzato dal compilatore per tutte le proprietà implementate automaticamente.

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

Il compilatore sintetizza 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, l'operazione di decostruzione restituisce 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