Les records (C# 9, C# 10)

Cet article fait partie d’une série d’articles sur les apports fonctionnels de C# 9.0 et C# 10.0.

C# 9 introduit un nouveau type d’objets dont le but est de fournir une syntaxe simple pour déclarer des objets de type référence contenant des propriétés. Ces objets peuvent être définis en utilisant le mot-clé record.

C# 10 complète les fonctionnalités des records en permettant de créer des records de type valeur similaires aux structs. Les records de type valeur peuvent être définis en utilisant le mot-clé record struct. A partir de C# 10, il est aussi possible d’utiliser la notation record class (notation équivalente à record).

Cet article a pour but de passer en revue les propriétés des objets record et record struct.

Suivant la façon dont ces objets sont instanciés, le compilateur peut rendre ces objets immutables et ajouter des méthodes à l’implémentation existante.

Il existe 2 syntaxes pour déclarer des objets records:

  • Une syntaxe condensée appelée positional record, par exemple:
    public record Car(string Brand, string Model); 
    

    Cette syntaxe permet de générer implicitement un objet immutable: les propriétés Car.Brand et Car.Model ne sont accessibles qu’en lecture seule.

  • Une syntaxe plus classique avec un constructeur:
    public record Car
    {
      public Car(string brand, string model)
      {
        this.Brand = brand;
        this.Model = model;
      }
    
      public string Brand { get; }
      public string Model { get; }
    }
    

    Les caractéristiques de l’objet record généré avec cette syntaxe sont explicites: les propriétés sont en lecture seule parce-qu’il n’existe que l’accesseur get.

C# 10.0

A partir de C# 10, on peut utiliser la notation record class (notation équivalente à record), par exemple:

public record class Car(string Brand, string Model);

ou

public record class Car
{
  // ...
}

De base, le compilateur rajoute des méthodes à l’implémentation des objets record de façon à faciliter leur utilisation et à étendre leurs caractéristiques:

  • La comparaison d’objets record se fait en comparant les propriétés membre des objets et non en comparant les références des objets (comme c’est le cas par défaut pour les objets de type référence).
  • Un constructeur est implicitement rajouté pour facilement instancier les objets record. Le constructeur rajouté permet d’affecter toutes les propriétés du record.
  • Ces objets peuvent être affichés directement avec ToString() sans avoir à surcharger cette méthode.
  • Ces objets supportent la déconstruction de ses propriétés sans effectuer d’implémentation particulière.

Ainsi si on définit un objet record de cette façon (syntaxe positional record):

public record Car(string Brand, string Model); 

Le compilateur va complêter l’implémentation pour produire une classe dont l’implémentation équivalente pourrait être:

public class Car 
{ 
  public string Brand { get; } // Propriétés accessibles en lecture seule
  public string Model { get; } 

  // Ajout d'un constructeur implicite
  public Car(string Brand, string Model) 
  {
    this.Brand = Brand;
    this.Model = Model;
  }   

  // Implémentation de ToString() 
  public override string ToString() 
  { 
    return $"Car {{ Brand = {this.Brand}, Model = {this.Model} }}"; 
  } 

  // Implémentation de Equals() 
  public override bool Equals(object obj) { ... } 
  public override int GetHashCode() { ... } 

  // Surcharge d'opérateurs 
  public static bool operator ==(object a, Car b) 
  { ... }; 

  public static bool operator !=(object a, Car b) 
  { ... }; 

  // Implémentation d'un déconstructeur
  public void Deconstruct(out string brand, out string model)  
    => (brand, model) = (this.Brand, this.Model); 
} 

Ainsi l’intérêt principal des objets record est de complêter l’implémentation en rajoutant des fonctions courantes sans avoir à surcharger la syntaxe.

Même si l’implémentation des objets record est complêtée par un constructeur ou des méthodes à la compilation, il reste possible d’implémenter d’autres constructeurs ou d’autres méthodes comme pour une classe:

public record Car 
{ 
  public string Brand { get; set; } 
  public string Model { get; set; } 

  public Car(string brand) 
  { 
    this.Brand = brand; 
    this.Model = string.Empty; 
  } 

  public string GetCompleteName() 
  { 
    return $"{this.Brand} {this.Model}"; 
  } 
} 

Un record est une classe ou structure

Un objet record peut être compilé sous forme d’une classe ou d’une struct (à partir de C# 10).

Si on déclare un record avec le mot-clé record, le code MSIL (i.e. MicroSoft Intermediate Language) obtenu si on compile le code C# suivant, contient des des instructions quasi similaires à celles d’une classe:

Code C# Instructions MSIL
public class CarAsClass
{ 
  public string Brand { get; init; }  
  public string Model { get; init; }  
} 
.class public auto ansi beforefieldinit Cs9.CarAsClass 
  extends [System.Runtime]System.Object
{ 
  //...  
} // End of class Cs9.CarAsClass 
public record CarAsRecord
{ 
  public string Brand { get; init; }  
  public string Model { get; init; }  
} 
.class public auto 
ansi beforefieldinit Cs9.CarAsRecord 
  extends [System.Runtime]System.Object 
  implements [System.Runtime]System.IEquatable`1<Cs9.CarAsRecord>
{ 
  // ... 
} // End of class Cs9.CarAsRecord 
Notation possible à partir de C# 10:

public record class CarAsRecord
{ 
  public string Brand { get; init; }  
  public string Model { get; init; }  
} 
C# 10.0

En déclarant un record de type valeur avec record struct (à partir de C# 10), le code MSIL est similaire à celui d’une struct:

Code C# Instructions MSIL
public struct CarAsStruct
{ 
  public string Brand { get; init; }  
  public string Model { get; init; }  
} 
.class public sequential ansi sealed 
  beforefieldinit Cs10.CarAsStruct
  extends [System.Runtime]System.ValueType
{
  //...
} // end of class Cs10.CarAsStruct
public record struct CarAsRecordStruct
{ 
  public string Brand { get; init; }  
  public string Model { get; init; }  
} 
.class public sequential ansi sealed 
  beforefieldinit Cs10.CarAsRecordStruct
  extends [System.Runtime]System.ValueType
  implements class [System.Runtime]System
    .IEquatable`1<valuetype Cs10.CarAsRecordStruct>
{
  //...
} // end of class Cs10.CarAsRecordStruct

La différence la plus notable entre les 2 couples d’objets est que le compilateur génère implicitement davantage de fonctions pour les objets record et record struct comme par exemple ToString(), PrintMembers(), Equals(), GetHashCode() etc…

Déclaration et construction

Comme indiqué plus haut, il existe 2 syntaxes pour déclarer des objets record ou record struct:

  • Une syntaxe explicite similaire à celle des classes ou structs: avec cette syntaxe, les accès aux propriétés sont explicitement implémentés.

    Par exemple:

    public record Car 
    { 
      public string Brand { get; set; } 
      public string Model { get; set; } 
    } 
    

    Un objet record déclaré de cette façon peut être instancié en utilisant des initializers:

    var car = new Car{ Brand = "Renault", Model = "4L" }; 
    
  • Une syntaxe condensée (i.e. positional record): les accès aux propriétés sont implicitement en lecture seule.

    Par exemple:

    public record Car(string Brand, string Model); 
    

    Dans ce cas, cet objet peut être instancié en utilisant un constructeur:

    var car = new Car("Renault", "4L"); 
    

    Ou en omettant le type après new:

    Car car  = new ("Renault", "4L"); 
    

    Avec cette construction, les propriétés sont en lecture seule:

    var brand = car.Brand; // OK 
    car.Brand = "Peugeot"; // ⚠ ERREUR ⚠
    

    D’autres constructions sont possibles pour préciser d’autres propriétés ou des méthodes en dehors du constructeur, par exemple:

    public record Car(string Brand, string Model) 
    { 
      public int Power { get; set; } 
    
      public string GetCompleteName() 
      { 
        return $"{this.Brand} {this.Model}"; 
      } 
    } 
    

    Avec cette dernière déclaration, on peut instancier en utilisant un initializer:

    var car  = new Car("Renault", "4L") { Power = 40 }; 
    
C# 10.0

Tous les éléments de syntaxe valable pour les records sont aussi valables pour les record structs, on peut utiliser:

  • Une syntaxe explicite similaire à celle des structs:
    public record struct Car 
    { 
      public string Brand { get; set; } 
      public string Model { get; set; } 
    }
    
  • Une syntaxe condensée (i.e. positional record):
    public record struct Car(string Brand, string Model);
    

Construction en utilisant une expression avec with

On peut instancier des objets record et record structs à partir d’autres objets en utilisant with, par exemple:

public record Car(string Brand, string Model); 

// ...

var sedan = new Car("Tesla", "3"); 
var suv = sedan with { Model = "X" }; 
C# 10.0

Depuis C# 10, il est possible d’utiliser l’opérateur with avec des structs (voir Amélioration des structures) et par suite avec les record structs:

public record struct Car(string Brand, string Model); 

// ...

var sedan = new Car("Tesla", "3"); 
var suv = sedan with { Model = "X" }; 

Héritage

Les objets record supportent l’héritage ce qui est un avantage par rapport aux objets struct, par exemple:

public record Vehicle 
{ 
  public Vehicle(int wheelCount, int doorCount) 
  { 
    this.WheelCount = wheelCount; 
    this.DoorCount = doorCount; 
  } 

  public int WheelCount { get; } 
  public int DoorCount { get; } 
} 

public record Car : Vehicle 
{ 
  public Car(string brand, string model, int wheelCount, int doorCount):  
    base(wheelCount, doorCount) 
  { 
    this.Brand = brand; 
    this.Model = model; 
  } 

  public string Brand { get; } 
  public string Model { get; } 
} 

Si on utilise la syntaxe positional record, l’implémentation équivalente est:

public record Vehicle(int WheelCount, int DoorCount); 

public record Car(string Brand, string Model, int WheelCount, int DoorCount): 
  Vehicle(WheelCount, DoorCount); 

Même si les objets record sont des classes, une classe ne peut pas hériter d’un record.

L’héritage n’est pas applicable aux record structs

⚠ L’héritage n’est pas supportée par les structures, par suite il n’est pas possible d’effectuer des héritages avec des record structs.

Comparaison

Comme indiqué plus haut, la comparaison entre des objets record (en tant qu’objet de type référence) est facilitée puisque qu’il n’est pas nécessaire de surcharger explicitement la fonction Equals(). Implicitement, le compilateur rajoute une implémentation de la fonction Equals() permettant de comparer toutes les propriétés, par exemple:

var car1 = new Car("Tesla", "3"); 
var car2 = new Car("Tesla", "3"); 
 
Console.WriteLine(car1.Equals(car2)); // true 

Les objets sont égaux par comparaison des valeurs des propriétés toutefois ils sont bien distincts en comparant les références:

Console.WriteLine(ReferenceEquals(car1, car2)); // false 

Les opérateurs d’égalité et d’inégalité sont surchargés:

Console.WriteLine(car1 == car2); // true 
Console.WriteLine(car1 != car2); // false 

Dans le cas des objets record struct, comme il s’agit d’objet de type valeur, le comportement est le même que pour les structs: les 2 objets sont égaux s’ils sont du même type et si leurs membres sont de même valeur. Toutefois la différence d’implémentation est que pour les structs, la comparaison se fait par réflexion alors que pour les record structs, le compilateur rajoute une fonction Equals().

Ainsi pour la comparaison, le comportement est le même pour les records et les records struct.

Surcharger Equals() et les opérateurs d’égalité et d’inégalités

Même si le compilateur rajoute implicitement une implémentation pour les fonctions:

  • bool Equals(Object object),
  • int GetHashCode() et
  • Des opérateurs == et =!.

Il est possible de proposer une autre implémentation pour GetHashCode() par override toutefois ce n’est pas possible pour bool Equals(Object object) et pour les opérateurs == et =!:

public record Car(string Brand, string Model) 
{ 
  public override bool Equals(Object obj) { ... } // ⚠ ERREUR ⚠
  public override int GetHashCode() {... } //OK 

  public static bool operator ==(Car car1, Car car2) => { ... } // ⚠ ERREUR ⚠
  public static bool operator !=(Car car1, Car car2) => { ... } // ⚠ ERREUR ⚠
} 

Il est possible de proposer une nouvelle implémentation Equals() si:

  • La signature n’est pas Equals(Object obj) et
  • Si la nouvelle implémentation est une fonction virtuelle ou si l’objet record est sealed (i.e. avec sealed il n’est pas possible d’hériter de l’objet record).

Par exemple, pour proposer une nouvelle implémentation de Equals() l’argument ne doit pas être de type object. Dans ce cas, on définit une nouvelle surcharge de Equals(), il n’y a plus d’override:

public record Car(string Brand, string Model) 
{ 
  public virtual bool Equals(Car car) { ... } // OK 
  public override int GetHashCode() {... }  
} 

Ou l’objet record doit être sealed:

public sealed record Car(string Brand, string Model) 
{ 
  public bool Equals(Car car) { ... } // OK 
  public override int GetHashCode() {... }  
} 

Dans ce cas, si on ne propose pas une implémentation pour GetHashCode(), un warning est généré à la compilation.

La comparaison prend en compte le type

Dans le cas de l’implémentation par défaut de Equals(), la comparaison implique la prise en compte des types des objets record. Ainsi même si les valeurs des membres sont identiques, le type des objets est pris en compte.

Par exemple si on considère des objets record héritant du même objet, possédant des propriétés identiques et dont les valeurs sont aussi identiques, les objets ne pourront être égaux puisqu’ils ne sont pas de même type.

Par exemple:

public record Vehicle(int WheelCount, int DoorCount); 

public record Car(string Brand, string Model, int WheelCount, int DoorCount): Vehicle(WheelCount, DoorCount); 

public record Truck(string Brand, string Model, int WheelCount, int DoorCount): Vehicle(WheelCount, DoorCount); 
 
// ...

var car = new Car { Brand = "Tesla",  Model = "3", WheelCount = 4, DoorCount = 4}; 
var truck = new Truck { Brand = "Tesla",  Model = "3", WheelCount = 4, DoorCount = 4}; 

Console.WriteLine(car.Equals(truck)); // False 
Console.WriteLine(car == truck); // False 

Immutabilité

L’accès en écriture des propriétés d’un objet record ou record struct est le même que pour, respectivement, une classe ou une structure. Un objet record ou record struct n’est pas forcément immatuble. On peut le rendre immutable comme pour une classe en n’utilisant des accesseurs n’autorisant que l’accès en lecture après la construction.

Plusieurs possibilités:

  • En omettant l’accesseur set; la propriété ne peut être initilisée que dans le constructeur:
    public record Car 
    { 
      public Car(string brand) 
      { 
        this.Brand = brand; // OK 
      } 
    
      public string Brand { get; } 
    } 
    
    // ...
    
    var car1  = new Car("Tesla"); // OK 
    car1.Brand = "Nio"; // ⚠ ERREUR ⚠ 
    
    var car2  = new Car{ Brand = "Nio" }; // ⚠ ERREUR ⚠ 
    
  • Un accesseur init; pour ne permettre l’initialisation qu’avec un constructeur ou un initializer.
    public record Car 
    { 
      public Car(string brand) 
      { 
        this.Brand = brand; // OK 
      } 
    
      public string Brand { get; init; } 
    } 
    
    // ...
    
    var car1  = new Car("Tesla"); // OK 
    car1.Brand = "Nio"; // ⚠ ERREUR ⚠ 
    
    var car2  = new Car{ Brand = "Tesla" }; // OK 
    
  • Utiliser un champ readonly et un accesseur init, l’affectation n’est possible que dans un constructeur ou avec un initializer:
    public record Car 
    { 
      private readonly string brand; 
    
      public Car(string brand) 
      { 
        this.Brand = brand; // OK 
      } 
    
      public string Brand  
      {  
        get => this.brand; 
        init => this.brand = value; 
      } 
    } 
    
    // ...
    
    var car1  = new Car("Tesla"); // OK 
    car1.Brand = "Nio"; // ⚠ ERREUR ⚠ 
     
    var car2 = new Car { Brand = "Nio" }; //OK  
    

record readonly struct

C# 10.0

A partir de C# 10, il est possible de définir des record structs immutables avec la notation readonly record struct. Le comportement est semblable aux objets readonly struct (apparus en C# 7.2): le compilateur vérifie que les membres de l’objet ne peuvent pas être modifiés en dehors d’une auto-initialisation directe d’un membre ou d’une initialisation dans le constructeur.

Ainsi pour un objet readonly record struct:

  • Une propriété ne pourra pas avoir d’accesseurs en écriture:
    public readonly record struct Car
    {  
      public string Brand { get; set; } // ERREUR  
      public string Model { get; } // OK  
    }
  • Les variables membres publiques doivent utiliser le mot-clé readonly:
    public readonly record struct Car
    {  
      public string Brand; // ERREUR  
      public readonly string Model; // OK  
    }
  • On ne peut pas modifier un membre même dans une fonction membre (en dehors du constructeur):
    public readonly record struct Car
    {  
      public readonly string Brand;
      public readonly string Model;
    
      public void ChangeMe(string newBrand)
      {
        Brand = newBrand;   // ERREUR
      }
    }
  • La déclaration d’évènements dans la structure n’est pas autorisée:
    public readonly record struct Car
    {  
      public event EventHandler Event; // ERREUR  
    }
  • Certaines modifications restent toutefois possibles comme, par exemple, modifier une liste:
    public readonly record struct Car
    {  
      public readonly List Items = new List();
    }
    
    // ...
    var car = new Car();
    car.Items.Add("new Item");   // OK
    

Implémentation implicite de ToString()

Pour les objets record et record struct, le compilateur génère une implémentation de ToString() de façon à afficher facilement les valeurs des propriétés.

Par exemple, si on considère l’objet suivant:

public record Car 
{ 
  public string Brand { get; init; } 
  public string Model { get; init; } 
} 

// ...

var car = new Car{ Brand = "Tesla", Model = "3" }; 
Console.WriteLine(car); 

On obtient:

Car { Brand = Tesla, Model = 3 } 

Il est possible de surcharger la méthode ToString():

public record Car 
{ 
  public string Brand { get; init; } 
  public string Model { get; init; } 

  public override string ToString() 
  { 
    // ... 
  } 
} 

La surcharge de ToString() est aussi possible pour les objets record struct.

PrintMembers()

Dans l’implémentation par défaut de ToString(), pour afficher les valeurs des propriétés, une fonction dont la signature est bool PrintMembers(StringBuilder stringBuilder) est rajoutée à la compilation et est appelée par ToString(). Cette fonction ajoute dans l’objet stringbuilder, les valeurs des propriétés.

Un exemple de l’implémentation de ToString() appelant PrintMembers() est:

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

On peut proposer une autre implémentation pour PrintMembers() toutefois il faut permettre que cette méthode soit surchargée dans un record qui hériterait du record où se trouve PrintMembers(). Ainsi 2 solutions sont possibles pour cette implémentation:

  • Implémenter une fonction PrintMembers() virtuelle et protected dans le cas des record et private dans le cas des record structs:
    public record Car 
    { 
      public string Brand { get; set; } 
      public string Model { get; set; } 
    
      protected virtual bool PrintMembers(StringBuilder stringBuilder) 
      { 
        // ... 
      } 
    } 
    

    Ou

    public record struct Car 
    { 
      public string Brand { get; set; } 
      public string Model { get; set; } 
    
      private virtual bool PrintMembers(StringBuilder stringBuilder) 
      { 
        // ... 
      } 
    } 
    
  • Si l’objet record est sealed (on ne peut donc pas en hériter), il faut implémenter PrintMembers() sous forme d’une fonction privée:

    public sealed record Car 
    { 
      public string Brand { get; set; } 
      public string Model { get; set; } 
    
      private bool PrintMembers(StringBuilder stringBuilder) 
      { 
        // ... 
      } 
    }
    

PrintMembers() dans le cas d’héritage

Dans le cas où l’objet record dérive d’un autre objet, les implémentations de PrintMembers() changent suivant si l’objet record est sealed ou non:

  • Si l’objet n’est pas sealed (c’est-à-dire qu’on peut en hériter), PrintMembers() doit être protected override:
    public record Vehicle 
    { 
      public int WheelCount { get; init; } 
      public int DoorCount { get; init; } 
    
      protected virtual bool PrintMembers(StringBuilder builder) 
      { 
        //...  
      } 
    } 
    
    public record Car : Vehicle 
    { 
      public string Brand { get; init; } 
      public string Model { get; init; } 
    
      protected override bool PrintMembers(StringBuilder builder) 
      { 
        //...  
      } 
    } 
    
  • Si l’objet est sealed (c’est-à-dire qu’on ne peut pas en hériter), PrintMembers() doit être une fonction protected sealed override:
    public record Vehicle 
    { 
      public int WheelCount { get; init; } 
      public int DoorCount { get; init; } 
    
      protected virtual bool PrintMembers(StringBuilder builder) 
      { 
        //...  
      } 
    } 
    
    public sealed record Car : Vehicle 
    { 
      public string Brand { get; init; } 
      public string Model { get; init; } 
    
      protected sealed override bool PrintMembers(StringBuilder builder) 
      { 
        //...  
      } 
    } 
    

Implémentation d’un déconstructeur avec la syntaxe positional record

Si on utilise la syntaxe positional record pour déclarer un objet record ou record struct, le compilateur rajoute une implémentation pour un constructeur et pour un deconstructeur.

Ainsi, si on considère un record défini de la façon suivante:

public record Car(string Brand, string Model); // déclaration avec la syntaxe positional record 

Un constructeur et un deconstructeur sont ajoutés à la compilation, on peut donc écrire:

var car = new Car("Tesla", "3"); // OK 

(string brand, string model) = car; // OK  
Ajout du constructeur et du deconstructeur par le compilateur si la syntaxe utilisée est positional record

L’ajout du constructeur et du deconstructeur n’est effectué par le compilateur que si on utilise la syntaxe positional record pour déclarer le record.

Si on définit le record de cette façon:

public record Car 
{ 
  public string Brand { get; set; } 
  public string Model { set; set; } 
} 

var car = new Car("Tesla", "3"); // ⚠ ERREUR ⚠, pas de constructeur 
(string brand, string model) = car; // ⚠ ERREUR ⚠, pas de déconstructeur 

Il est possible de rajouter un deconstructeur explicitement à un objet record (au même titre que le constructeur):

public record Car 
{ 
  public Car(string brand, string model)  
  { 
    this.Brand = brand; 
    this.Model = model; 
  } 

  public string Brand { get; set; } 
  public string Model { set; set; } 
  
  public void Deconstruct(out string brand, out string model)  
    => (brand, model) = (this.Brand, this.Model); 
} 

Pour résumer…

Un objet record est une classe ou une structure dans laquelle le compilateur rajoute implicitement une implémentation pour des fonctions usuelles:

  • En C# 9: il est seulement possible de créer des objets record qui sont des objets de type référence avec la notation record.
  • A partir de C# 10: on peut utiliser la notation record class pour créer des objets record de type référence et on peut utiliser des records de type valeur avec la notation record struct.
  • Enfin, C# 10 permet aussi de créer des records de type valeur immutables avec la notation readonly record struct.

Les fonctions usuelles pouvant être rajoutées par le compilateur dans le cas des objets record ou record struct sont: des constructeurs, Equals(), GetHashCode(), ToString(), PrintMembers(), un déconstructeur et des surcharges pour les opérateurs d’égalité et d’inégalité.

2 syntaxes permettent de déclarer des objets record et record struct:

  • Une syntaxe classique proche de celle des classes ou des structures: dans ce cas, l’objet record ou record struct n’est pas forcément immutable, c’est l’implémentation qui détermine explicitement les propriétés de l’objet.
  • Une syntaxe condensée appelée positional record qui permet d’implémenter un objet immutable avec un constructeur permettant d’affecter toutes les propriétés, par exemple pour déclarer un objet record:
    public record Car(string Brand, string Model); 
    

    Ce record peut être instancié en utilisant le constructeur implicite:

    var car = new Car("Car brand", "Car model");
    

    Pour déclarer un objet record struct:

    public record struct Car(string Brand, string Model); 
    
Classe
(class)
Record
(record)
Structure
(struct)
Record struct
(record struct)
Type d’objet Objet de type référence Objet de type valeur
Manipulation des variables, passage de paramètre Par copie de référence Par copie de valeur
Stockage Dans le tas managé Dans la pile mais peut être stocké dans le tas managé, par exemple:

  • si la struct contient une référence
  • en cas de boxing
Peut être statique
Oui
Non
Héritage
Supporté
Non
Constructeur sans paramètre Implicite en cas d’absence d’implémentation de constructeur Avant C# 11: un constructeur sans paramètre ne peut pas être implémenté. Implicite en cas d’absence d’implémentation de constructeur
Constructeur de copie
Non

(A implémenter explicitement)

Oui

si on utilise with

Toute affectation est une copie
Immutable
Non
Non
Non
Non
Oui

si on utilise la syntaxe positional record

Oui

si on utilise la syntaxe positional record

Comportement par défaut en cas de comparaison (avec ==, != ou Equals()) Comparaison des références Comparaison des données membre
Surchage de Equals(Object obj)
Possible
Impossible
Possible
Impossible
Comportement par défaut de ToString() Chaine contenant le type de la classe Chaine contenant un affichage amélioré des valeurs des membres Chaine contenant le type de la struct Chaine contenant un affichage amélioré des valeurs des membres
Type d’objet dans le code MSIL .class
extends System.Object
.class
extends System.Object
implements class System.IEquatable<>
.class
extends System.ValueType
.class
extends System.ValueType
implements class System.IEquatable<>
Comportement en cas d'absence d'opérateur de portée des membres Privé par défaut
Durée de vie gérée par le Garbage Collector Oui Non
0 responses... add one

Leave a Reply