Les nouveautés de C# 6 (2) : les constructeurs primaires

Une des grosses nouveautés de C# 6 est l’arrivée des constructeurs primaires. L’équipe derrière le langage C# est partie du postulat que la grande majorité du travail effectué dans le constructeur d’une classe est l’initialisation de variables et de propriétés. Les constructeurs primaires visent dans ce cas de figure à alléger le volume de code sans qu’il perde en lisibilité. Notons d’ailleurs que ces nouvelles fonctionnalités bénéficient à la fois aux classes et aux structures.

Partons d’un cas simple :

Des classes et structures avec des paramètres

public class Customer
{
    private string first;
    private string last;

    public Customer(string first, string last)
    {
        this.first = first;
        this.last = last;
    }

    public string First { get { return first; } }
    public string Last { get { return last; } }
}

Dans ce cas, le constructeur sert effectivement uniquement à initialiser des propriétés. Dans C# 6, il sera maintenant possible de l’écrire de la façon suivante :

public class Customer(string first, string last)
{
    public string First { get; } = first;
    public string Last { get; } = last;
}

Notre classe a maintenant des paramètres, signifiant qu’elle utilise un constructeur primaire. A l’aide d’auto-propriétés sans setter (voir l’article précédent) que nous initialisons directement, nous divisons le nombre de lignes de code par deux. Les paramètres first et last sont accessibles sur l’ensemble de la portée de la classe, de l’accolade ouvrante à l’accolade fermante. Notons que si notre classe a un constructeur primaire, elle ne bénéficie plus du constructeur implicite par défaut sans paramètre, exactement comme dans le cas d’un constructeur déclaré explicitement.

Corps des constructeurs primaires

Néanmoins, il y a évidemment des cas où l’on ne fait pas qu’initialiser des propriétés ou des variables. Dans ce cas, nous pouvons déclarer un corps à notre constructeur primaire, simplement en lui donnant une portée entre accolades :

public class Customer(string first, string last)
{
    {
        if (first == null) throw new ArgumentNullException("first");
        if (last == null) throw new ArgumentNullException("last");
    }

    public string First { get; } = first;
    public string Last { get; } = last;
}

C’est surement cette nouvelle syntaxe qui demandera le plus de temps d’adaptation. Je ne sais pas encore trop quoi en penser personnellement. En revanche, attention, l’affectation de first à First se fait AVANT le test de nullité, ce qui parait relativement illogique. Je ne sais pas encore si c’est une erreur d’implémentation ou un comportement attendu, ni si ce comportement changera entre aujourd’hui et la version finale. Deuxième bizarrerie : le scope du constructeur primaire peut se situer n’importe où dans la classe, ce qui peut encore nuire à la lisibilité du code.

Constructeurs explicites et constructeurs primaires

Une classe a souvent besoin de plusieurs constructeurs. Une classe avec un constructeur primaire peut également se voir dotée de constructeurs explicites. Néanmoins, afin de garantir que le constructeur primaire soit appelé avec le bon nombre de paramètres, chacun des constructeurs explicites devra appeler un constructeur this(…) :

public Customer() : this("Jane", "Doe") {} // appelle le constructeur primaire

Les constructeurs explicites peuvent s’appeler entre eux, mais doivent forcément finir par appeler indirectement le constructeur primaire. Le constructeur primaire devra donc naturellement être le constructeur le plus “précis”, alors que les constructeurs explicites se chargeront de charger des valeurs de paramètres par défaut.

A noter enfin qu’il est possible de déclarer des constructeurs primaires avec des valeurs par défaut :

class Customer(string first = "Jane", string last = "Doe")
{
    public string First { get; set; } = first;
    public string Last { get; set; } = last;
}

La panoplie semble donc bien complète. Voilà de quoi alléger sensiblement le volume de code de nos classes.

Constructeur de la classe de base

Le constructeur primaire, comme tout constructeur, appelle toujours implicitement ou explicitement le constructeur de la base de base, dont on hérite. Si aucun appel n’est spécifié, le constructeur sans paramètre de la classe de base est appelé. Voici comment appeler explicitement un constructeur de base :

class BufferFullException() : Exception("Buffer full") { … }

Il est parfaitement possible d’écrire de façon classique une classe qui hérite d’une classe mère avec un constructeur primaire :

class SuperCustomer : Customer
{
  public string City { get; set; }

  public SuperCustomer(string first, string last, string city) : base(first, last)
  {
    City = city;
  }
}

Mais la classe héritée peut elle aussi avoir un constructeur primaire. Dans ce cas, elle devra appeler explicitement le constructeur primaire de la classe mère :

class SuperCustomer(string first, string last, string city) : Customer(first, last)
{
  public string City { get; set; } = city;
}

Si le constructeur de la classe mère n’est pas appelé correctement, le compilateur lève une exception de type « Error 3 There is no argument given that corresponds to the required formal parameter ‘first’ of ‘ThePoint.Customer.Customer(string, string)' »

Portée de l’initialisation

Les paramètres du constructeur primaire vivent dans leur propre portée. Il n’y donc aucun risque de conflit avec le nom d’un autre membre de la classe :

public class Customer(string first, string last, long id) : Person(first, last)
{
    private long id = id;
}

Classes partielles

Si une classe est déclarée partiellement, seule une partie peut déclarer des paramètres, et seule cette partie peut également déclarer les arguments d’une classe de base dont elle hérite. En revanche, les paramètres du constructeur de base sont accessibles depuis toutes les parties de la classe.

Tags: C#, vNext,

Pas de commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *