Aller au contenu

Rendre obligatoire l'initialisation de propriétés

Introduction

Lorsqu'une classe possède plusieurs propriétés, nous souhaitons parfois que certaines d'entre elles soient indispensables au bon fonctionnement des objets qu'elle permet de créer.

Exemple sur une classe

Voici une première implémentation de la classe Customer :

public class Customer
{
    public string FirstName { get; set; } = string.Empty;

    public string LastName { get; set; } = string.Empty;

    public string Email { get; set; } = string.Empty;
}

Rien ne nous empêche d'instancier cette classe avec le constructeur par défaut, généré automatiquement par le compilateur, puisqu'aucun constructeur n'a été explicitement implémenté.

var customer = new Customer();

L'objet customer est créé et ses propriétés sont initialisées avec une chaîne de caractères vide. Cette situation peut entraîner des erreurs ou nécessiter des vérifications supplémentaires, dans le code lors de la manipulation de ces propriétés.

Depuis la version 11 du langage C#, le mot-clé required permet d'indiquer qu'une propriété doit obligatoirement être initialisée, lors de la création d'un objet. Par exemple, décidons que les propriétés FirstName et LastName doivent être valorisées :

public class Customer
{
    public required string FirstName { get; set; }

    public required string LastName { get; set; }

    public string? Email { get; set; }
}

Si nous conservons cette écriture pour instancier la classe Customer :

var customer = new Customer();

Une erreur apparaît lors de la compilation :

Required member 'Customer.FirstName' must be set in the object initializer.

En effet, le compilateur s'attend à ce que les propriétés définies avec le mot clé required soient initialisées lors de l'instanciation :

var customer = new Customer()
{
    FirstName = "James",
    LastName = "RAVAILLE"
};

Ainsi, le code compile.

Remarque importante :

Initialisée ne signifie pas nécessairement valorisée avec une donnée valide. Si vous spécifiez la valeur null et que le type du paramètre est de type référence nullable, alors le compilateur n'indiquera aucune erreur ou avertissement.

Impacts sur les constructeurs

Lorsqu'un constructeur initialise lui-même les propriétés obligatoires, il est possible d'indiquer au compilateur que ce constructeur satisfait les contraintes grâce à l'attribut SetsRequiredMembers :

public class Customer
{
    public required string FirstName { get; set; }

    public required string LastName { get; set; }

    [SetsRequiredMembers]
    public Customer(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

Sans l'utilisation de cette métadonnée, le compilateur peut signaler que les propriétés requises (marquées avec required) ne sont pas initialisées. Et dans ce cas, nous devrions implémenter :

var customer = new Customer("James", "Ravaille")
{
    FirstName = "James",
    LastName = "Ravaille"
};

Et cette implémentation est une mauvaise pratique par la duplication des données, d'où l'utilité de SetsRequiredMembers.

required remplace-t-il les constructeurs ?

La réponse est : non. Ils ne jouent pas les mêmes rôles. L'utilisation de required garantit qu'une propriété est initialisée, alors qu'un constructeur permet d'imposer des règles métier et des validations, et indiquer comment valoriser les propriétés. Ils ne s'opposent pas et sont même complémentaires.