Condividi tramite


Creare tipi di record

i Record sono tipi che usano uguaglianza basata su valori. È possibile definire i record come tipi di riferimento o tipi di valore. Due variabili di un tipo di record sono uguali se le definizioni dei tipi di record sono identiche e, se per ogni campo, i valori in entrambi i record sono uguali. Due variabili di un tipo di classe sono uguali se gli oggetti a cui si fa riferimento sono lo stesso tipo di classe e le variabili fanno riferimento allo stesso oggetto. L'uguaglianza basata sui valori implica altre capacità che probabilmente vorreste nei tipi di record. Il compilatore genera molti di questi membri quando si dichiara un record anziché un class. Il compilatore genera gli stessi metodi per i tipi record struct.

In questa esercitazione si apprenderà come:

  • Decidi se aggiungere il modificatore record a un tipo class.
  • Dichiarare i tipi di record e i tipi di record posizionali.
  • Sostituisci i tuoi metodi con quelli generati dal compilatore nei record.

Prerequisiti

È necessario configurare il computer per eseguire .NET 6 o versione successiva. Il compilatore C# è disponibile con Visual Studio 2022 o .NET SDK.

Caratteristiche dei record

Definisci un record dichiarando un tipo con la parola chiave record, modificando una dichiarazione class o struct. Facoltativamente, è possibile omettere la parola chiave class per creare un record class. Un record segue la semantica di uguaglianza basata su valori. Per applicare la semantica dei valori, il compilatore genera diversi metodi per il tipo di record (sia per i tipi record class che per i tipi record struct):

I record forniscono anche un override di Object.ToString(). Il compilatore sintetizza i metodi per la visualizzazione dei record usando Object.ToString(). Questi membri vengono esaminati durante la scrittura del codice per questa esercitazione. I record supportano le espressioni with per consentire la mutazione non distruttiva dei record.

È anche possibile dichiarare record posizionali usando una sintassi più concisa. Il compilatore genera più metodi per te quando dichiari record posizionali.

  • Costruttore primario i cui parametri corrispondono ai parametri posizionali nella dichiarazione di record.
  • Proprietà pubbliche per ogni parametro di un costruttore primario. Queste proprietà sono solo init per i tipi record class e readonly record struct. Per i tipi record struct, sono di lettura/scrittura.
  • Metodo Deconstruct per estrarre le proprietà dal record.

Creare dati sulla temperatura

I dati e le statistiche sono tra gli scenari in cui si desidera usare i record. Per questa esercitazione si crea un'applicazione che calcola giorni di grado per usi diversi. I gradi giorno sono una misura del calore (o della mancanza di calore) in un periodo di giorni, settimane o mesi. I giorni di grado tengono traccia e stimano l'utilizzo dell'energia. Più giorni più caldi significano più aria condizionata, e più giorni più freddi significano più utilizzo forno. I giorni di grado aiutano a gestire le popolazioni vegetali e correlare alla crescita delle piante man mano che cambiano le stagioni. I giorni di grado aiutano a tenere traccia delle migrazioni degli animali per le specie che viaggiano per soddisfare il clima.

La formula si basa sulla temperatura media in un determinato giorno e una temperatura di base. Per calcolare i giorni di grado nel tempo, è necessaria la temperatura elevata e bassa ogni giorno per un periodo di tempo.To compute degree days over time, you'll need the high and low temperature each day for a period of time. Per iniziare, creare una nuova applicazione. Creare una nuova applicazione console. Creare un nuovo tipo di record in un nuovo file denominato "DailyTemperature.cs":

public readonly record struct DailyTemperature(double HighTemp, double LowTemp);

Il codice precedente definisce un record posizionale . Il record DailyTemperature è un readonly record struct, perché non si intende ereditare da esso e deve essere non modificabile. Le proprietà HighTemp e LowTemp sono proprietà di sola inizializzazione , ovvero possono essere impostate nel costruttore o usando un inizializzatore di proprietà. Se si desideravi che i parametri posizionali fossero leggibili e scrivibili, si dichiara un record struct anziché un readonly record struct. Il tipo DailyTemperature dispone anche di un costruttore primario con due parametri che corrispondono alle due proprietà. Usi il costruttore primario per inizializzare un record DailyTemperature. Il codice seguente crea e inizializza diversi record DailyTemperature. Il primo utilizza parametri con nome per chiarire HighTemp e LowTemp. Gli inizializzatori rimanenti usano parametri posizionali per inizializzare il HighTemp e LowTemp:

private static DailyTemperature[] data = [
    new DailyTemperature(HighTemp: 57, LowTemp: 30), 
    new DailyTemperature(60, 35),
    new DailyTemperature(63, 33),
    new DailyTemperature(68, 29),
    new DailyTemperature(72, 47),
    new DailyTemperature(75, 55),
    new DailyTemperature(77, 55),
    new DailyTemperature(72, 58),
    new DailyTemperature(70, 47),
    new DailyTemperature(77, 59),
    new DailyTemperature(85, 65),
    new DailyTemperature(87, 65),
    new DailyTemperature(85, 72),
    new DailyTemperature(83, 68),
    new DailyTemperature(77, 65),
    new DailyTemperature(72, 58),
    new DailyTemperature(77, 55),
    new DailyTemperature(76, 53),
    new DailyTemperature(80, 60),
    new DailyTemperature(85, 66) 
];

È possibile aggiungere proprietà o metodi personalizzati ai record, inclusi i record posizionali. È necessario calcolare la temperatura media per ogni giorno. È possibile aggiungere tale proprietà al record DailyTemperature:

public readonly record struct DailyTemperature(double HighTemp, double LowTemp)
{
    public double Mean => (HighTemp + LowTemp) / 2.0;
}

Assicurarsi di poter usare questi dati. Aggiungere il codice seguente al metodo Main:

foreach (var item in data)
    Console.WriteLine(item);

** Esegui l'applicazione e vedrai un output simile al seguente (diverse righe rimosse per lo spazio):

DailyTemperature { HighTemp = 57, LowTemp = 30, Mean = 43.5 }
DailyTemperature { HighTemp = 60, LowTemp = 35, Mean = 47.5 }


DailyTemperature { HighTemp = 80, LowTemp = 60, Mean = 70 }
DailyTemperature { HighTemp = 85, LowTemp = 66, Mean = 75.5 }

Il codice precedente mostra l'output dell'override di ToString sintetizzato dal compilatore. Se si preferisce testo diverso, è possibile scrivere la propria versione di ToString che impedisce al compilatore di sintetizzare una versione.

Calcolare i gradi giorno

Per calcolare i giorni di grado, si prende la differenza da una temperatura di base e dalla temperatura media in un determinato giorno. Per misurare il calore nel tempo, si eliminano tutti i giorni in cui la temperatura media è inferiore alla linea di base. Per misurare il freddo nel tempo, si eliminano tutti i giorni in cui la temperatura media è superiore alla baseline. Ad esempio, usa 65 F come base per i giorni di riscaldamento e raffreddamento. Questa è la temperatura in cui non è necessario riscaldamento o raffreddamento. Se un giorno ha una temperatura media di 70 F, quel giorno è di cinque giorni di raffreddamento e zero giorni di riscaldamento. Viceversa, se la temperatura media è 55 F, quel giorno è di 10 giorni di riscaldamento e 0 giorni di raffreddamento.

È possibile esprimere queste formule come una piccola gerarchia di tipi di record: un tipo astratto di giorno grado e due tipi concreti per i giorni grado di riscaldamento e i giorni grado di raffreddamento. Questi tipi possono anche essere record posizionali. Accettano una temperatura di base e una sequenza di record di temperatura giornalieri come argomenti per il costruttore primario:

public abstract record DegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords);

public sealed record HeatingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
    : DegreeDays(BaseTemperature, TempRecords)
{
    public double DegreeDays => TempRecords.Where(s => s.Mean < BaseTemperature).Sum(s => BaseTemperature - s.Mean);
}

public sealed record CoolingDegreeDays(double BaseTemperature, IEnumerable<DailyTemperature> TempRecords)
    : DegreeDays(BaseTemperature, TempRecords)
{
    public double DegreeDays => TempRecords.Where(s => s.Mean > BaseTemperature).Sum(s => s.Mean - BaseTemperature);
}

Il record DegreeDays astratto è la classe base condivisa per i record HeatingDegreeDays e CoolingDegreeDays. Le dichiarazioni del costruttore primario nei record derivati mostrano come gestire l'inizializzazione dei record di base. Il record derivato dichiara i parametri 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. In questo esempio i record derivati non aggiungono nuovi parametri del costruttore primario. Testare il codice aggiungendo il codice seguente al metodo Main:

var heatingDegreeDays = new HeatingDegreeDays(65, data);
Console.WriteLine(heatingDegreeDays);

var coolingDegreeDays = new CoolingDegreeDays(65, data);
Console.WriteLine(coolingDegreeDays);

Si ottiene un output simile al seguente:

HeatingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 85 }
CoolingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 71.5 }

Definire metodi sintetizzati dal compilatore

Il codice calcola il numero corretto di giorni di riscaldamento e raffreddamento in quel periodo di tempo. In questo esempio viene illustrato il motivo per cui è possibile sostituire alcuni dei metodi sintetizzati per i record. È possibile dichiarare la propria versione di uno dei metodi sintetizzati dal compilatore in un tipo di record, ad eccezione del metodo clone. Il metodo clone ha un nome generato dal compilatore e non è possibile fornire un'implementazione diversa. Questi metodi sintetici includono un costruttore di copia, i membri dell'interfaccia System.IEquatable<T>, i test di uguaglianza e disuguaglianza e GetHashCode(). A questo scopo, sintetizzi PrintMembers. Puoi anche dichiarare il tuo ToString, ma PrintMembers offre un'opzione migliore per gli scenari di ereditarietà. Per fornire una versione personalizzata di un metodo sintetizzato, la firma deve corrispondere al metodo sintetizzato.

Nell'output della console, l'elemento TempRecords non è utile. Visualizza il tipo, ma nient'altro. È possibile modificare questo comportamento fornendo la propria implementazione del metodo sintetizzato PrintMembers. La firma dipende dai modificatori applicati alla dichiarazione di record:

  • Se un tipo di record è sealedo un record struct, la firma è private bool PrintMembers(StringBuilder builder);
  • Se un tipo di record non è sealed e deriva da object (cioè, non dichiara un record di base), la firma è protected virtual bool PrintMembers(StringBuilder builder);.
  • Se un tipo di record non è sealed e deriva da un altro record, la firma è protected override bool PrintMembers(StringBuilder builder);

Queste regole sono più semplici da comprendere attraverso la comprensione dello scopo di PrintMembers. PrintMembers aggiunge informazioni su ogni proprietà di un tipo di record a una stringa. Il contratto richiede alle registrazioni di base di aggiungere i loro membri alla visualizzazione e presume che i membri derivati aggiungano i propri membri. Ogni tipo di record sintetizza un'override di tipo ToString simile all'esempio sotto per HeatingDegreeDays:

public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append("HeatingDegreeDays");
    stringBuilder.Append(" { ");
    if (PrintMembers(stringBuilder))
    {
        stringBuilder.Append(" ");
    }
    stringBuilder.Append("}");
    return stringBuilder.ToString();
}

Dichiari un metodo PrintMembers nel record DegreeDays che non stampa il tipo della raccolta:

protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
    stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
    return true;
}

La firma dichiara un metodo virtual protected in modo che corrisponda alla versione del compilatore. Non preoccuparti se sbagli gli accessor; il linguaggio di programmazione impone la firma corretta. Se si dimenticano i modificatori corretti per qualsiasi metodo sintetizzato, il compilatore genera avvisi o errori che consentono di ottenere la firma corretta.

È possibile dichiarare il metodo ToString come sealed in un tipo di record. Ciò impedisce ai record derivati di fornire una nuova implementazione. I record derivati continueranno a contenere l'override PrintMembers. Se non si vuole visualizzare il tipo di runtime del record, si sigillerebbe ToString. Nell'esempio precedente si perderebbero le informazioni su dove il record misurava i giorni di riscaldamento o raffreddamento.

Mutazione non distruttiva

I membri sintetizzati in una classe di record posizionale non modificano lo stato del record. L'obiettivo è che è possibile creare più facilmente record non modificabili. Ricorda che si dichiara readonly record struct per creare una struttura record non modificabile. Esaminare di nuovo le dichiarazioni precedenti per HeatingDegreeDays e CoolingDegreeDays. I membri aggiunti svolgono calcoli sui valori del record, ma non modificano lo stato. I record posizionali semplificano la creazione di tipi riferimento non modificabili.

La creazione di tipi riferimento non modificabili implica l'uso di una mutazione non distruttiva. È possibile creare nuove istanze di record simili alle istanze di record esistenti usando espressioni with. Queste espressioni sono una costruzione di copia con assegnazioni aggiuntive che modificano la copia. Il risultato è una nuova istanza di record in cui ogni proprietà è stata copiata dal record esistente e, facoltativamente, modificata. Il record originale rimane invariato.

Aggiungiamo un paio di funzionalità al tuo programma che illustrano le espressioni with. Creare prima di tutto un nuovo record per calcolare i giorni di gradi in crescita usando gli stessi dati. giorni di grado crescente in genere usa 41 F come baseline e misura le temperature al di sopra della baseline. Per usare gli stessi dati, è possibile creare un nuovo record simile al coolingDegreeDays, ma con una temperatura di base diversa:

// Growing degree days measure warming to determine plant growing rates
var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);

È possibile confrontare il numero di gradi calcolati con i numeri generati con una temperatura di base superiore. Ricorda che i record sono tipi di riferimento e queste copie sono copie superficiali. La matrice per i dati non viene copiata, ma entrambi i record fanno riferimento agli stessi dati. Questo è un vantaggio in un altro scenario. Per i giorni in crescita, è utile tenere traccia del totale per i cinque giorni precedenti. È possibile creare nuovi record con dati di origine diversi usando with espressioni. Il codice seguente compila una raccolta di questi accumuli, quindi visualizza i valori:

// showing moving accumulation of 5 days using range syntax
List<CoolingDegreeDays> movingAccumulation = new();
int rangeSize = (data.Length > 5) ? 5 : data.Length;
for (int start = 0; start < data.Length - rangeSize; start++)
{
    var fiveDayTotal = growingDegreeDays with { TempRecords = data[start..(start + rangeSize)] };
    movingAccumulation.Add(fiveDayTotal);
}
Console.WriteLine();
Console.WriteLine("Total degree days in the last five days");
foreach(var item in movingAccumulation)
{
    Console.WriteLine(item);
}

È anche possibile usare le espressioni with per creare copie di record. Non specificare nessuna proprietà tra le parentesi graffe per l'espressione with. Ciò significa creare una copia e non modificare le proprietà:

var growingDegreeDaysCopy = growingDegreeDays with { };

Eseguire l'applicazione completata per visualizzare i risultati.

Sommario

In questa guida sono stati illustrati diversi aspetti dei record. I record forniscono una sintassi concisa per i tipi in cui l'uso fondamentale archivia i dati. Per le classi orientate agli oggetti, l'uso fondamentale è la definizione delle responsabilità. Questa esercitazione è incentrata sui record posizionali , in cui è possibile usare una sintassi concisa per dichiarare le proprietà di un record. Il compilatore sintetizza diversi membri del record per la copia e il confronto dei record. È possibile aggiungere eventuali altri membri di cui hai bisogno per i tipi di record. È possibile creare tipi di record non modificabili sapendo che nessuno dei membri generati dal compilatore cambierebbe lo stato. Le espressioni with semplificano il supporto delle mutazioni non distruttive.

I record aggiungono un altro modo per definire i tipi. Si utilizzano le definizioni class per creare gerarchie orientate agli oggetti che si concentrano sulle responsabilità e sul comportamento degli oggetti. Crei tipi struct di strutture di dati che archiviano i dati e sono sufficientemente piccoli da copiare in modo efficiente. Crei tipi record quando desideri uguaglianza e confronto basati sui valori, vuoi evitare di copiare i valori e preferisci utilizzare variabili di riferimento. Si creano tipi record struct quando si desiderano le funzionalità dei record per un tipo abbastanza piccolo da essere copiato in modo efficiente.

Per ulteriori informazioni sui record, consultare l'articolo di riferimento sul linguaggio C# per il tipo di record, la proposta di specifica del tipo di record e la specifica della struttura di record .