Depuis .NET 3.0, il est possible d’affecter des propriétés dans la même instruction que le constructeur. On peut ainsi écrire :
Rectangle r = new Rectangle() { Largeur = 2, Longueur = 3 };
Ceci s’avère très pratique, car on peut choisir les propriétés à affecter, et éviter ainsi de devoir créer de nombreux constructeurs pour les différents cas possibles :
public Rectangle() : this(1, 1) { } public Rectangle(int Longueur) : this(Longueur, 1) { } public Rectangle(int Longueur, int Largeur) { this.Longueur = Longueur; this.Largeur = Largeur; }
Attention toutefois, car il y a une petite différence entre le dernier constructeur juste au-dessus et la première ligne de code : la première grammaire exécute le code supplémentaire, mais pas dans le cadre du constructeur lui-même. Ce code est exécuté après. On pourrait se dire que ce n’est pas essentiel, mais imaginons que nous faisons un calcul lors de la mise en place du constructeur :
public Rectangle(int Longueur, int Largeur) { this.Longueur = Longueur; this.Largeur = Largeur; this.Aire = this.Longueur * this.Largeur; }
Si on appelle alors le constructeur vide avec les instructions d’affectation après, ceci est complètement équivalent à :
Rectangle r = new Rectangle();
r.Largeur = 2;
r.Longueur = 3;
Et dans ce cas, l’aire reste à 0, alors que la largeur et la hauteur ne sont pas nulles, car le calcul de l’aire est réalisé avant que les valeurs de propriété ne soient affectées.
On pourra objecter, par contre, deux choses : la première est que, lorsqu’on affecte la largeur ou la hauteur, il faut de toute façon relancer le calcul d’aire. La seconde est que ce genre de calcul ne devrait en théorie pas être réalisé dans un constructeur, qui est censé être très rapide et ne jamais remonter d’exception. Dans notre cas, il suffirait que le calcul soit une division au lieu d’une multiplication pour permettre ce comportement, qui est à proscrire absolument : un développeur ne doit pas avoir à anticiper une exception sur un constructeur.
Au final, il ne faut donc pas se passer de cette facilité d’écriture, mais tout simplement bien prendre soin de traiter tous les cas. Et proposer un constructeur complet est de toute façon une bonne pratique pour que le développeur utilisant votre classe sache à quoi s’en tenir. Par contre, inutile de créer toutes les surcharges des signatures de constructeur
Le code permettant de régler ces problèmes est le suivant :
public class Rectangle { private int _Longueur = 1; public int Longueur { get { return _Longueur; } set { if (value < 0) throw new ArgumentException("Valeur négative interdite", "Longueur"); _Longueur = value; CalculerAire(); } } private int _Largeur = 1; public int Largeur { get { return _Largeur; } set { if (value < 0) throw new ArgumentException("Valeur négative interdite", "Largeur"); _Largeur = value; CalculerAire(); } } protected void CalculerAire() { this.Aire = this.Longueur * this.Largeur; } public int Aire { get; private set; } public Rectangle() { } public Rectangle(int Longueur, int Largeur) : this() { this.Longueur = Longueur; this.Largeur = Largeur; } }
Note : dans ce cas simpliste, il faudrait bien sûr plutôt une structure, ce qui permettrait d’obtenir une encapsulation forte par immutabilité, et donc un risque de bug réduit. Le but ici est simplement de faire voir le fonctionnement des affectations suivant le constructeur, et de montrer qu’une mauvaise utilisation peut poser problème. De la même manière, le calcul de l’aire est tellement simple qu’on aurait plutôt tendance à le refaire à chaque appel de la propriété correspondante.