Enregistrements (référence C#)

À compter de C# 9, vous utilisez le record mot clé pour définir un type de référence qui fournit des fonctionnalités intégrées pour encapsuler des données. C# 10 permet à la record class syntaxe en tant que synonyme de clarifier un type de référence et record struct de définir un type valeur avec des fonctionnalités similaires. Vous pouvez créer des types d’enregistrements avec des propriétés immuables à l’aide de paramètres positionnels ou de syntaxe de propriété standard.

Les deux exemples suivants illustrent record des types de référence (ou record class) :

public record Person(string FirstName, string LastName);
public record Person
{
    public string FirstName { get; init; } = default!;
    public string LastName { get; init; } = default!;
};

Les deux exemples suivants illustrent record struct les types de valeurs :

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; }
}

Vous pouvez également créer des enregistrements avec des propriétés et des champs mutables :

public record Person
{
    public string FirstName { get; set; } = default!;
    public string LastName { get; set; } = default!;
};

Les structs d’enregistrement peuvent également être mutables, à la fois les structs d’enregistrement positionnel et les structs d’enregistrement sans paramètres positionnels :

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; }
}

Bien que les enregistrements puissent être mutables, ils sont principalement destinés à prendre en charge des modèles de données immuables. Le type d’enregistrement offre les fonctionnalités suivantes :

Les exemples précédents montrent certaines distinctions entre les enregistrements qui sont des types de référence et des enregistrements qui sont des types valeur :

  • Un record ou un record class type de référence déclare un type de référence. Le class mot clé est facultatif, mais peut ajouter de la clarté pour les lecteurs. Un record struct type de valeur déclare un type de valeur.
  • Les propriétés positionnelles sont immuables dans un record class et un readonly record struct. Ils sont mutables dans un record struct.

Le reste de cet article traite à la fois record class des types et record struct des types. Les différences sont détaillées dans chaque section. Vous devez décider entre un record class et un record struct similaire à décider entre un et un classstruct. L’enregistrement de terme est utilisé pour décrire le comportement qui s’applique à tous les types d’enregistrements. L’un record struct ou record class l’autre est utilisé pour décrire le comportement qui s’applique uniquement aux types de struct ou de classe, respectivement. Le record type a été introduit en C# 9 ; record struct les types ont été introduits en C# 10.

Syntaxe positionnelle pour la définition de propriété

Vous pouvez utiliser des paramètres positionnels pour déclarer des propriétés d’un enregistrement et initialiser les valeurs de propriété lorsque vous créez une instance :

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 }
}

Lorsque vous utilisez la syntaxe positionnelle pour la définition de propriété, le compilateur crée :

  • Propriété implémentée automatiquement publique pour chaque paramètre positionnel fourni dans la déclaration d’enregistrement.
    • Pour record les types et readonly record struct les types : propriété init seule .
    • Pour record struct les types : propriété en lecture-écriture.
  • Constructeur principal dont les paramètres correspondent aux paramètres positionnels de la déclaration d’enregistrement.
  • Pour les types de struct d’enregistrement, constructeur sans paramètre qui définit chaque champ sur sa valeur par défaut.
  • Méthode Deconstruct avec un out paramètre pour chaque paramètre positionnel fourni dans la déclaration d’enregistrement. La méthode déconstructe les propriétés définies à l’aide de la syntaxe positionnelle; il ignore les propriétés définies à l’aide de la syntaxe de propriété standard.

Vous pouvez ajouter des attributs à l’un de ces éléments que le compilateur crée à partir de la définition d’enregistrement. Vous pouvez ajouter une cible à n’importe quel attribut que vous appliquez aux propriétés de l’enregistrement positionnel. L’exemple suivant applique la System.Text.Json.Serialization.JsonPropertyNameAttribute propriété de Person chaque enregistrement. La property: cible indique que l’attribut est appliqué à la propriété générée par le compilateur. D’autres valeurs doivent field: appliquer l’attribut au champ et param: appliquer l’attribut au paramètre.

/// <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);

L’exemple précédent montre également comment créer des commentaires de documentation XML pour l’enregistrement. Vous pouvez ajouter la balise pour ajouter de la <param> documentation pour les paramètres du constructeur principal.

Si la définition de propriété implémentée automatiquement générée n’est pas ce que vous souhaitez, vous pouvez définir votre propre propriété du même nom. Par exemple, vous pouvez modifier l’accessibilité ou la mutabilité, ou fournir une implémentation pour l’accesseur ou set l’accesseurget. Si vous déclarez la propriété dans votre source, vous devez l’initialiser à partir du paramètre positionnel de l’enregistrement. Si votre propriété est une propriété implémentée automatiquement, vous devez initialiser la propriété. Si vous ajoutez un champ de stockage dans votre source, vous devez initialiser le champ de stockage. Le décostructeur généré utilisera votre définition de propriété. Par exemple, l’exemple suivant déclare les propriétés et les FirstName propriétés d’un enregistrement publicpositionnel, mais limite le Id paramètre positionnel à internal.LastName Vous pouvez utiliser cette syntaxe pour les types d’enregistrements et de struct d’enregistrement.

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 type d’enregistrement n’a pas à déclarer de propriétés positionnelles. Vous pouvez déclarer un enregistrement sans aucune propriété positionnelle, et vous pouvez déclarer d’autres champs et propriétés, comme dans l’exemple suivant :

public record Person(string FirstName, string LastName)
{
    public string[] PhoneNumbers { get; init; } = Array.Empty<string>();
};

Si vous définissez des propriétés à l’aide de la syntaxe de propriété standard, mais omettez le modificateur d’accès, les propriétés sont implicitement private.

Immuabilité

Un enregistrement positionnel et un struct d’enregistrement positionnel déclarent des propriétés init uniquement. Un struct d’enregistrement positionnel déclare les propriétés en lecture-écriture. Vous pouvez remplacer l’une de ces valeurs par défaut, comme indiqué dans la section précédente.

L’immuabilité peut être utile lorsque vous avez besoin d’un type centré sur les données pour être thread-safe ou que vous dépendez d’un code de hachage restant le même dans une table de hachage. Toutefois, l’immuabilité n’est pas appropriée pour tous les scénarios de données. Entity Framework Core, par exemple, ne prend pas en charge la mise à jour avec des types d’entités immuables.

Les propriétés init uniquement, que ce soit créées à partir de paramètres positionnels (record classet readonly record struct) ou en spécifiant des init accesseurs, ont une immuabilité peu profonde. Après l’initialisation, vous ne pouvez pas modifier la valeur des propriétés de type valeur ou la référence des propriétés de type référence. Toutefois, les données auxquelles une propriété de type référence fait référence peuvent être modifiées. L’exemple suivant montre que le contenu d’une propriété immuable de type référence (un tableau dans ce cas) est mutable :

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
}

Les fonctionnalités uniques aux types d’enregistrements sont implémentées par des méthodes synthétisées par le compilateur, et aucune de ces méthodes ne compromisse l’immuabilité en modifiant l’état de l’objet. Sauf spécification, les méthodes synthétisées sont générées pour record, record structet readonly record struct les déclarations.

Égalité des valeurs

Si vous ne remplacez pas ou remplacez les méthodes d’égalité, le type que vous déclarez détermine comment l’égalité est définie :

  • Pour class les types, deux objets sont égaux s’ils font référence au même objet en mémoire.
  • Pour struct les types, deux objets sont égaux s’ils sont de même type et stockent les mêmes valeurs.
  • Pour record les types, y compris record struct et readonly record struct, deux objets sont égaux s’ils sont de même type et stockent les mêmes valeurs.

La définition de l’égalité pour un record struct est identique à celle d’un struct. La différence est que pour un struct, l’implémentation est dans ValueType.Equals(Object) et s’appuie sur la réflexion. Pour les enregistrements, l’implémentation est synthétisée et utilise les membres de données déclarés.

L’égalité des références est requise pour certains modèles de données. Par exemple, Entity Framework Core dépend de l’égalité de référence pour s’assurer qu’elle utilise une seule instance d’un type d’entité pour ce qui est conceptuellement une seule entité. Pour cette raison, les enregistrements et les structs d’enregistrement ne sont pas appropriés pour être utilisés en tant que types d’entités dans Entity Framework Core.

L’exemple suivant illustre l’égalité des valeurs des types d’enregistrements :

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
}

Pour implémenter l’égalité des valeurs, le compilateur synthétise plusieurs méthodes, notamment :

  • Remplacement de Object.Equals(Object). Il s’agit d’une erreur si le remplacement est déclaré explicitement.

    Cette méthode est utilisée comme base pour la Object.Equals(Object, Object) méthode statique lorsque les deux paramètres ne sont pas null.

  • A virtual, ou sealed, Equals(R? other)R est le type d’enregistrement. Cette méthode implémente IEquatable<T>. Cette méthode peut être déclarée explicitement.

  • Si le type d’enregistrement est dérivé d’un type Based’enregistrement de base , Equals(Base? other). Il s’agit d’une erreur si le remplacement est déclaré explicitement. Si vous fournissez votre propre implémentation, Equals(R? other)fournissez également une implémentation.GetHashCode

  • Remplacement de Object.GetHashCode(). Cette méthode peut être déclarée explicitement.

  • Remplacements des opérateurs == et !=. Il s’agit d’une erreur si les opérateurs sont déclarés explicitement.

  • Si le type d’enregistrement est dérivé d’un type d’enregistrement de base, protected override Type EqualityContract { get; };. Cette propriété peut être déclarée explicitement. Pour plus d’informations, consultez Égalité dans les hiérarchies d’héritage.

Si un type d’enregistrement a une méthode qui correspond à la signature d’une méthode synthétisée autorisée à être déclarée explicitement, le compilateur ne synthétise pas cette méthode.

Mutation non destructeur

Si vous devez copier une instance avec certaines modifications, vous pouvez utiliser une expression pour obtenir une withmutation non destructeur. Une with expression crée une nouvelle instance d’enregistrement qui est une copie d’une instance d’enregistrement existante, avec des propriétés et des champs spécifiés modifiés. Vous utilisez la syntaxe d’initialiseur d’objet pour spécifier les valeurs à modifier, comme illustré dans l’exemple suivant :

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’expression with peut définir des propriétés de position ou des propriétés créées à l’aide de la syntaxe de propriété standard. Les propriétés non positionnelles doivent avoir un init ou set un accesseur à modifier dans une with expression.

Le résultat d’une with expression est une copie superficielle, ce qui signifie que pour une propriété de référence, seule la référence à une instance est copiée. L’enregistrement d’origine et la copie se terminent par une référence à la même instance.

Pour implémenter cette fonctionnalité pour record class les types, le compilateur synthétise une méthode clone et un constructeur de copie. La méthode de clonage virtuel retourne un nouvel enregistrement initialisé par le constructeur de copie. Lorsque vous utilisez une with expression, le compilateur crée du code qui appelle la méthode clone, puis définit les propriétés spécifiées dans l’expression with .

Si vous avez besoin d’un comportement de copie différent, vous pouvez écrire votre propre constructeur de copie dans un record class. Si vous le faites, le compilateur n’en synthétisera pas un. Faites de votre constructeur private si l’enregistrement est sealed, sinon faites-le protected. Le compilateur ne synthétise pas de constructeur de copie pour record struct les types. Vous pouvez en écrire un, mais le compilateur ne génère pas d’appels pour with les expressions. Les valeurs du fichier sont copiées lors de l’affectation record struct .

Vous ne pouvez pas remplacer la méthode clone et vous ne pouvez pas créer de membre nommé Clone dans n’importe quel type d’enregistrement. Le nom réel de la méthode clone est généré par le compilateur.

Mise en forme intégrée pour l’affichage

Les types d’enregistrements ont une méthode générée par ToString le compilateur qui affiche les noms et les valeurs des propriétés et des champs publics. La ToString méthode retourne une chaîne au format suivant :

<nom> du type d’enregistrement { <nom> de propriété = <valeur>, <nom> de propriété = <valeur>, ...}

La chaîne imprimée est <value> la chaîne retournée par le ToString() type de la propriété. Dans l’exemple suivant, ChildNames est un System.Array, où ToString retourne System.String[]:

Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }

Pour implémenter cette fonctionnalité, dans record class les types, le compilateur synthétise une méthode virtuelle PrintMembers et un ToString remplacement. Dans record struct les types, ce membre est private. Le ToString remplacement crée un StringBuilder objet avec le nom de type suivi d’un crochet ouvrant. Il appelle PrintMembers pour ajouter des noms et des valeurs de propriété, puis ajoute le crochet fermant. L’exemple suivant montre du code similaire à ce que contient le remplacement synthétisé :

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();
}

Vous pouvez fournir votre propre implémentation ou PrintMembers le ToString remplacement. Des exemples sont fournis dans la mise en forme dans laPrintMembers section enregistrements dérivés plus loin dans cet article. En C# 10 et versions ultérieures, votre implémentation ToString peut inclure le sealed modificateur, ce qui empêche le compilateur de synthétiser une ToString implémentation pour tous les enregistrements dérivés. Pour ce faire, vous pouvez créer une représentation sous forme de chaîne cohérente dans une hiérarchie de record types. (Les enregistrements dérivés auront toujours une PrintMembers méthode générée pour toutes les propriétés dérivées.)

Héritage

Cette section s’applique uniquement aux record class types.

Un enregistrement peut hériter d’un autre enregistrement. Toutefois, un enregistrement ne peut pas hériter d’une classe et une classe ne peut pas hériter d’un enregistrement.

Paramètres positionnels dans les types d’enregistrements dérivés

L’enregistrement dérivé déclare des paramètres positionnels pour tous les paramètres du constructeur principal de l’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.

L’exemple suivant illustre l’héritage avec la syntaxe de propriété positionnelle :

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 }
}

Égalité dans les hiérarchies d’héritage

Cette section s’applique aux types, mais pas record struct aux record class types. Pour que deux variables d’enregistrement soient égales, le type d’exécution doit être égal. Les types des variables conteneur peuvent être différents. La comparaison d’égalité héritée est illustrée dans l’exemple de code suivant :

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
}

Dans l’exemple, toutes les variables sont déclarées en tant que Person, même lorsque l’instance est un type dérivé de l’un ou Teacherl’autre Student . Les instances ont les mêmes propriétés et les mêmes valeurs de propriété. Mais student == teacher retourne False bien que les deux sont des Personvariables -type, et student == student2 retourne True bien qu’une variable est une Person variable et une Student variable. Le test d’égalité dépend du type d’exécution de l’objet réel, et non du type déclaré de la variable.

Pour implémenter ce comportement, le compilateur synthétise une EqualityContract propriété qui retourne un Type objet qui correspond au type de l’enregistrement. Les EqualityContract méthodes d’égalité permettent aux méthodes d’égalité de comparer le type d’exécution des objets lorsqu’ils vérifient l’égalité. Si le type de base d’un enregistrement est object, cette propriété est virtual. Si le type de base est un autre type d’enregistrement, cette propriété est une substitution. Si le type d’enregistrement est sealed, cette propriété est effectivement sealed parce que le type est sealed.

Lors de la comparaison de deux instances d’un type dérivé, les méthodes d’égalité synthétisées vérifient toutes les propriétés de la base et des types dérivés pour l’égalité. La méthode synthétisée GetHashCode utilise la GetHashCode méthode de toutes les propriétés et champs déclarés dans le type de base et le type d’enregistrement dérivé.

with expressions dans les enregistrements dérivés

Le résultat d’une with expression a le même type d’exécution que l’opérande de l’expression. Toutes les propriétés du type d’exécution sont copiées, mais vous ne pouvez définir que les propriétés du type de compilation, comme l’illustre l’exemple suivant :

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 mise en forme dans les enregistrements dérivés

La méthode synthétisée PrintMembers d’un type d’enregistrement dérivé appelle l’implémentation de base. Le résultat est que toutes les propriétés et champs publics des types dérivés et de base sont inclus dans la ToString sortie, comme illustré dans l’exemple suivant :

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 }
}

Vous pouvez fournir votre propre implémentation de la PrintMembers méthode. Si vous procédez ainsi, utilisez la signature suivante :

  • Pour un sealed enregistrement qui dérive de object (ne déclare pas d’enregistrement de base) : private bool PrintMembers(StringBuilder builder);
  • Pour un sealed enregistrement qui dérive d’un autre enregistrement (notez que le type englobant est sealed, de sorte que la méthode est efficace sealed) : protected override bool PrintMembers(StringBuilder builder);
  • Pour un enregistrement qui n’est pas sealed et dérive de l’objet : protected virtual bool PrintMembers(StringBuilder builder);
  • Pour un enregistrement qui n’est pas sealed et dérive d’un autre enregistrement : protected override bool PrintMembers(StringBuilder builder);

Voici un exemple de code qui remplace les méthodes synthétisées PrintMembers , une pour un type d’enregistrement qui dérive de l’objet et un pour un type d’enregistrement qui dérive d’un autre enregistrement :

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 }
}

Notes

En C# 10 et versions ultérieures, le compilateur synthétisera PrintMembers dans des enregistrements dérivés même lorsqu’un enregistrement de base a scellé la ToString méthode. Vous pouvez également créer votre propre implémentation de PrintMembers.

Comportement de déconstructeur dans les enregistrements dérivés

La Deconstruct méthode d’un enregistrement dérivé retourne les valeurs de toutes les propriétés positionnelles du type de compilation. Si le type de variable est un enregistrement de base, seules les propriétés d’enregistrement de base sont déconstructées, sauf si l’objet est converti en type dérivé. L’exemple suivant illustre l’appel d’un déconstructeur sur un enregistrement dérivé.

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
}

Contraintes génériques

Il n’existe aucune contrainte générique qui nécessite qu’un type soit un enregistrement. Les enregistrements répondent à la ou struct à la class contrainte. Pour créer une contrainte sur une hiérarchie spécifique de types d’enregistrements, placez la contrainte sur l’enregistrement de base comme vous le feriez pour une classe de base. Pour plus d’informations, consultez Contraintes sur les paramètres de type.

spécification du langage C#

Pour plus d’informations, consultez la section Classes de la spécification du langage C#.

Pour plus d’informations sur les fonctionnalités introduites dans C# 9 et versions ultérieures, consultez les notes de proposition de fonctionnalités suivantes :

Voir aussi