Créer des types d’enregistrements
Les Enregistrements sont des types qui utilisent l’égalité basée sur la valeur. Vous pouvez définir des enregistrements en tant que types de référence ou types valeur. Deux variables d’un type d’enregistrement sont égales si les définitions de type d’enregistrement sont identiques et, si pour chaque champ, les valeurs des deux enregistrements sont égales. Deux variables d’un type de classe sont égales si les objets référencés sont le même type de classe et que les variables font référence au même objet. L’égalité basée sur la valeur implique d’autres fonctionnalités que vous souhaitez probablement dans les types d’enregistrements. Le compilateur génère un grand nombre de ces membres lorsque vous déclarez un record
au lieu d’un class
. Le compilateur génère ces mêmes méthodes pour les types record struct
.
Dans ce tutoriel, vous allez apprendre à :
- Déterminez si vous ajoutez le modificateur
record
à un type declass
. - Déclarez les types d’enregistrements et les types d’enregistrements positionnels.
- Remplacez vos méthodes par les méthodes générées par le compilateur dans les enregistrements.
Conditions préalables
Vous devez configurer votre ordinateur pour exécuter .NET 6 ou version ultérieure. Le compilateur C# est disponible avec Visual Studio 2022 ou le sdk .NET .
Caractéristiques des enregistrements
Vous définissez un enregistrement en déclarant un type avec le mot clé record
, en modifiant une déclaration de class
ou de struct
. Si vous le souhaitez, vous pouvez omettre le mot clé class
pour créer un record class
. Un enregistrement suit la sémantique d’égalité basée sur la valeur. Pour appliquer la sémantique des valeurs, le compilateur génère plusieurs méthodes pour votre type d’enregistrement (à la fois pour les types record class
et les types record struct
) :
- Un remplacement de Object.Equals(Object).
- Méthode de
Equals
virtuelle dont le paramètre est le type d’enregistrement. - Un remplacement de Object.GetHashCode().
- Méthodes pour
operator ==
etoperator !=
. - Les types d’enregistrements implémentent System.IEquatable<T>.
Les enregistrements fournissent également un remplacement de Object.ToString(). Le compilateur synthétise les méthodes d’affichage des enregistrements à l’aide de Object.ToString(). Vous explorez ces membres lorsque vous écrivez le code de ce didacticiel. Les enregistrements prennent en charge les expressions with
pour activer la mutation non destructrice des enregistrements.
Vous pouvez également déclarer des enregistrements positionnels à l’aide d’une syntaxe plus concise. Le compilateur synthétise d’autres méthodes pour vous lorsque vous déclarez des enregistrements positionnels :
- Constructeur principal dont les paramètres correspondent aux paramètres positionnels sur la déclaration d’enregistrement.
- Des propriétés publiques pour chaque paramètre d’un constructeur principal. Ces propriétés sont init uniquement pour les types
record class
et les typesreadonly record struct
. Pour les typesrecord struct
, elles sont en lecture-écriture. - Méthode
Deconstruct
pour extraire les propriétés de l’enregistrement.
Générer des données de température
Les données et les statistiques font partie des scénarios dans lesquels vous souhaitez utiliser des enregistrements. Pour ce didacticiel, vous allez créer une application qui calcule les degrés jours pour différentes utilisations. Les degrés jours sont une mesure de la chaleur (ou de l’absence de chaleur) sur une période de jours, de semaines ou de mois. Les degrés jours permettent de suivre et de prévoir la consommation d’énergie. Les jours plus chauds signifient plus de climatisation, et les jours plus froids signifient plus d’utilisation du four. Les degrés jours aident à gérer les populations de plantes et à mettre en corrélation la croissance des plantes au fil des saisons. Les degrés-jours aident à suivre les migrations animales pour les espèces qui voyagent en fonction du climat.
La formule est basée sur la température moyenne sur un jour donné et une température de référence. Pour calculer les degrés-jours sur une période de temps, vous aurez besoin des températures maximale et minimale chaque jour pour une période donnée. Commençons par créer une application. Créez une nouvelle application console. Créez un type d’enregistrement dans un nouveau fichier nommé « DailyTemperature.cs » :
public readonly record struct DailyTemperature(double HighTemp, double LowTemp);
Le code précédent définit un enregistrement positionnel . L’enregistrement DailyTemperature
est une readonly record struct
, car vous n’avez pas l’intention d’en hériter et il doit être immuable. Les propriétés HighTemp
et LowTemp
sont des propriétés à initialisation unique , ce qui signifie qu'elles peuvent être définies dans le constructeur ou à l'aide d'un initialiseur de propriété. Si vous souhaitez que les paramètres positionnels soient en lecture-écriture, vous déclarez une record struct
au lieu d’un readonly record struct
. Le type DailyTemperature
a également un constructeur principal qui a deux paramètres qui correspondent aux deux propriétés. Vous utilisez le constructeur principal pour initialiser un enregistrement de DailyTemperature
. Le code suivant crée et initialise plusieurs enregistrements DailyTemperature
. La première utilise des paramètres nommés pour clarifier les HighTemp
et les LowTemp
. Les initialiseurs restants utilisent des paramètres positionnels pour initialiser les HighTemp
et les 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)
];
Vous pouvez ajouter vos propres propriétés ou méthodes aux enregistrements, y compris les enregistrements positionnels. Vous devez calculer la température moyenne pour chaque jour. Vous pouvez ajouter cette propriété à l’enregistrement DailyTemperature
:
public readonly record struct DailyTemperature(double HighTemp, double LowTemp)
{
public double Mean => (HighTemp + LowTemp) / 2.0;
}
Nous allons vous assurer que vous pouvez utiliser ces données. Ajoutez le code suivant à votre méthode Main
:
foreach (var item in data)
Console.WriteLine(item);
Exécutez votre application et vous voyez la sortie qui ressemble à l’affichage suivant (plusieurs lignes supprimées pour l’espace) :
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 }
Le code précédent montre le résultat du remplacement de ToString
synthétisé par le compilateur. Si vous préférez un texte différent, vous pouvez écrire votre propre version de ToString
qui empêche le compilateur de synthétiser une version pour vous.
Calculer les degrés jours
Pour calculer des jours de degrés, vous prenez la différence entre une température de référence et la température moyenne d’un jour donné. Pour mesurer la chaleur au fil du temps, vous ignorez tous les jours où la température moyenne est inférieure à la température de référence. Pour mesurer le froid au fil du temps, vous ignorez tous les jours où la température moyenne est supérieure à la valeur de référence. Par exemple, les États-Unis utilisent 65 F comme base pour les jours de chauffage et de refroidissement. C’est la température où aucun chauffage ou refroidissement n’est nécessaire. Si un jour a une température moyenne de 70 F (21,1° C), ce jour nécessite cinq degrés jours de climatisation et zéro degré jour de chauffage. En revanche, si la température moyenne est de 12,8 °C, cela représente 10 degrés-jours de chauffage et 0 degrés-jours de refroidissement.
Vous pouvez exprimer ces formules sous la forme d'une petite hiérarchie de types d'enregistrements : un type abstrait de degrés-jours et deux types concrets pour les degrés-jours de chauffage et les degrés-jours de refroidissement. Ces types peuvent également être des enregistrements positionnels. Ils prennent une température de référence et une séquence d’enregistrements de température quotidiens en tant qu’arguments pour le constructeur principal :
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);
}
L’enregistrement DegreeDays
abstrait est la classe de base partagée pour les enregistrements HeatingDegreeDays
et CoolingDegreeDays
. Les déclarations de constructeur primaires sur les enregistrements dérivés montrent comment gérer l’initialisation des enregistrements de base. Votre enregistrement dérivé déclare des paramètres pour tous les paramètres du constructeur principal d’enregistrement de base. L’enregistrement de base déclare et initialise ces propriétés. L’enregistrement dérivé ne les masque pas, mais crée et initialise uniquement les propriétés des paramètres qui ne sont pas déclarés dans son enregistrement de base. Dans cet exemple, les enregistrements dérivés n’ajoutent pas de nouveaux paramètres de constructeur principal. Testez votre code en ajoutant le code suivant à votre méthode de Main
:
var heatingDegreeDays = new HeatingDegreeDays(65, data);
Console.WriteLine(heatingDegreeDays);
var coolingDegreeDays = new CoolingDegreeDays(65, data);
Console.WriteLine(coolingDegreeDays);
Vous obtenez une sortie similaire à l’affichage suivant :
HeatingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 85 }
CoolingDegreeDays { BaseTemperature = 65, TempRecords = record_types.DailyTemperature[], DegreeDays = 71.5 }
Définir des méthodes synthétisées par le compilateur
Votre code calcule le nombre correct de jours de chauffage et de refroidissement au cours de cette période. Toutefois, cet exemple montre pourquoi vous souhaiterez peut-être remplacer certaines des méthodes synthétisées pour les enregistrements. Vous pouvez déclarer votre propre version de l’une des méthodes synthétisées par le compilateur dans un type d’enregistrement, à l’exception de la méthode clone. La méthode clone a un nom généré par le compilateur et vous ne pouvez pas fournir d’implémentation différente. Ces méthodes synthétisées incluent un constructeur de copie, les membres de l’interface System.IEquatable<T>, les tests d’égalité et d’inégalité et GetHashCode(). À cet effet, vous synthétisez PrintMembers
. Vous pouvez également déclarer votre propre ToString
, mais PrintMembers
offre une meilleure option pour les scénarios d’héritage. Pour fournir votre propre version d’une méthode synthétisée, la signature doit correspondre à la méthode synthétisée.
L’élément TempRecords
dans la sortie de la console n’est pas utile. Il affiche le type, mais rien d’autre. Vous pouvez modifier ce comportement en fournissant votre propre implémentation de la méthode de PrintMembers
synthétisée. La signature dépend des modificateurs appliqués à la déclaration record
:
- Si un type d’enregistrement est
sealed
ou unrecord struct
, la signature estprivate bool PrintMembers(StringBuilder builder);
- Si un type d’enregistrement n’est pas
sealed
et dérive deobject
(autrement dit, il ne déclare pas d’enregistrement de base), la signature estprotected virtual bool PrintMembers(StringBuilder builder);
- Si un type d’enregistrement n’est pas
sealed
et dérive d’un autre enregistrement, la signature estprotected override bool PrintMembers(StringBuilder builder);
Ces règles sont plus faciles à comprendre en comprenant l’objectif de PrintMembers
. PrintMembers
ajoute des informations sur chaque propriété d’un type d’enregistrement à une chaîne de caractères. Le contrat nécessite des enregistrements de référence pour ajouter leurs membres à l’affichage et suppose que les membres dérivés ajouteront leurs membres. Chaque type d’enregistrement synthétise un remplacement de ToString
semblable à l’exemple suivant pour HeatingDegreeDays
:
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("HeatingDegreeDays");
stringBuilder.Append(" { ");
if (PrintMembers(stringBuilder))
{
stringBuilder.Append(" ");
}
stringBuilder.Append("}");
return stringBuilder.ToString();
}
Vous déclarez une méthode PrintMembers
dans le registre DegreeDays
qui n'imprime pas le type de la collection :
protected virtual bool PrintMembers(StringBuilder stringBuilder)
{
stringBuilder.Append($"BaseTemperature = {BaseTemperature}");
return true;
}
La signature déclare une méthode virtual protected
pour qu’elle corresponde à la version du compilateur. Ne vous inquiétez pas si vous recevez mal les accesseurs ; le langage applique la signature correcte. Si vous oubliez les modificateurs corrects pour toute méthode synthétisée, le compilateur émet des avertissements ou des erreurs qui vous aident à obtenir la bonne signature.
Vous pouvez déclarer la méthode ToString
comme sealed
dans un type d’enregistrement. Cela empêche les enregistrements dérivés de fournir une nouvelle implémentation. Les enregistrements dérivés contiendront toujours le remplacement de PrintMembers
. Vous devez sceller ToString
si vous ne souhaitez pas qu’il affiche le type d’exécution de l’enregistrement. Dans l'exemple précédent, vous perdez les informations concernant l'endroit où l'enregistrement mesure les degrés-jours de chauffage ou de refroidissement.
Mutation non destructeur
Les membres synthétisés dans une classe d’enregistrement positionnelle ne modifient pas l’état de l’enregistrement. L’objectif est que vous pouvez créer plus facilement des enregistrements immuables. N’oubliez pas que vous déclarez un readonly record struct
pour créer une structure d’enregistrement immuable. Examinez à nouveau les déclarations précédentes pour HeatingDegreeDays
et CoolingDegreeDays
. Les membres ajoutés effectuent des calculs sur les valeurs de l’enregistrement, mais ne mutent pas l’état. Les enregistrements positionnels facilitent la création de types de référence immuables.
La création de types de référence immuables signifie que vous souhaitez utiliser une mutation non destructeur. Vous créez de nouvelles instances d’enregistrement similaires aux instances d’enregistrement existantes à l’aide d’expressions with
. Ces expressions sont une construction de copie avec des affectations supplémentaires qui modifient la copie. Le résultat est une nouvelle instance d’enregistrement où chaque propriété a été copiée à partir de l’enregistrement existant et éventuellement modifiée. L’enregistrement d’origine n’est pas modifié.
Ajoutons quelques petites fonctions à votre programme pour illustrer les expressions with
. Tout d’abord, créons un enregistrement pour calculer des degrés jours de croissance à l’aide des mêmes données. jours de degré croissant utilise généralement 41 F comme base de référence et mesure les températures au-dessus de la ligne de base. Pour utiliser les mêmes données, vous pouvez créer un enregistrement similaire à la coolingDegreeDays
, mais avec une température de base différente :
// Growing degree days measure warming to determine plant growing rates
var growingDegreeDays = coolingDegreeDays with { BaseTemperature = 41 };
Console.WriteLine(growingDegreeDays);
Vous pouvez comparer le nombre de degrés calculés aux nombres générés avec une température de référence plus élevée. N’oubliez pas que les enregistrements sont des types de référence et que ces copies sont des copies superficielles. Le tableau des données n’est pas copié, mais les deux enregistrements font référence aux mêmes données. Ce fait est un avantage dans un autre scénario. Pour les jours de degrés croissants, il est utile de suivre le total des cinq jours précédents. Vous pouvez créer de nouveaux enregistrements avec différentes données sources à l’aide d’expressions with
. Le code suivant génère une collection de ces accumulations, puis affiche les valeurs :
// 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);
}
Vous pouvez également utiliser with
expressions pour créer des copies d’enregistrements. Ne spécifiez aucune propriété entre les accolades de l’expression with
. Cela signifie créer une copie et ne modifiez aucune propriété :
var growingDegreeDaysCopy = growingDegreeDays with { };
Exécutez l’application terminée pour afficher les résultats.
Résumé
Ce didacticiel a montré plusieurs aspects des enregistrements. Les enregistrements fournissent une syntaxe concise pour les types où l’utilisation fondamentale stocke des données. Pour les classes orientées objet, l’utilisation fondamentale définit les responsabilités. Ce tutoriel s’est concentré sur enregistrements positionnels, où vous pouvez utiliser une syntaxe concise pour déclarer les propriétés d’un enregistrement. Le compilateur synthétise plusieurs membres de l’enregistrement pour la copie et la comparaison d’enregistrements. Vous pouvez ajouter tous les autres membres dont vous avez besoin pour vos types d’enregistrements. Vous pouvez créer des types d’enregistrements immuables sachant qu’aucun des membres générés par le compilateur ne mutait l’état. Les expressions with
facilitent le support des mutations non destructrices.
Les enregistrements ajoutent une autre façon de définir des types. Vous utilisez class
définitions pour créer des hiérarchies orientées objet qui se concentrent sur les responsabilités et le comportement des objets. Vous créez struct
types pour les structures de données qui stockent les données et sont suffisamment petits pour les copier efficacement. Vous créez record
types lorsque vous souhaitez une égalité et une comparaison basées sur la valeur, ne souhaitez pas copier de valeurs et souhaitez utiliser des variables de référence. Vous créez des types de record struct
lorsque vous souhaitez que les fonctionnalités des enregistrements pour un type qui est assez petit pour être copié efficacement.
Vous pouvez en savoir plus sur les enregistrements dans l’article de référence du langage C# pour le type d’enregistrement, la spécification proposée de type d’enregistrement , et la spécification de struct d’enregistrement .