Aller au contenu principal

Constructeurs

Notions théoriques

Qu'est-ce qu'un constructeur ?

Un constructeur est une méthode spéciale appelée automatiquement lors de la création d'un objet avec new. Il garantit que l'objet est dans un état valide dès sa création.

class Etudiant
{
public string Nom { get; set; }
public double Note { get; set; }

// Constructeur : même nom que la classe, sans type de retour
public Etudiant(string nom, double note)
{
Nom = nom;
Note = note;
}
}

// Appel du constructeur avec new
var alice = new Etudiant("Alice", 15.5);
Console.WriteLine($"{alice.Nom} : {alice.Note}/20");

Constructeur par défaut

Si vous ne définissez aucun constructeur, C# génère automatiquement un constructeur par défaut (sans paramètres). Dès que vous en définissez un, le constructeur par défaut disparaît.

class A { } // constructeur par défaut implicite
var a = new A(); // OK

class B
{
public B(string nom) { } // constructeur explicite avec paramètre
}
var b = new B("test"); // OK
// var b2 = new B(); // ERREUR : plus de constructeur par défaut !

Surcharge de constructeurs

On peut définir plusieurs constructeurs avec des signatures différentes (types ou nombre de paramètres) :

class Rectangle
{
public double Largeur { get; }
public double Hauteur { get; }

// Constructeur 1 : dimensions précises
public Rectangle(double largeur, double hauteur)
{
Largeur = largeur;
Hauteur = hauteur;
}

// Constructeur 2 : carré (même côté)
public Rectangle(double cote) : this(cote, cote)
{
// délègue au constructeur 1 avec cote, cote
}

// Constructeur 3 : sans paramètres (rectangle unitaire)
public Rectangle() : this(1, 1) { }

public double Aire() => Largeur * Hauteur;
public double Perimetre() => 2 * (Largeur + Hauteur);
}

var r1 = new Rectangle(4, 3); // 4 × 3
var r2 = new Rectangle(5); // carré 5 × 5
var r3 = new Rectangle(); // 1 × 1

: this(...) — délégation de constructeur

La syntaxe : this(...) permet à un constructeur d'appeler un autre constructeur de la même classe. Cela évite la duplication de code d'initialisation.

class Produit
{
public string Nom { get; }
public decimal Prix { get; }
public int Stock { get; }

// Constructeur principal
public Produit(string nom, decimal prix, int stock)
{
if (string.IsNullOrWhiteSpace(nom))
throw new ArgumentException("Le nom ne peut pas être vide.", nameof(nom));
if (prix < 0)
throw new ArgumentOutOfRangeException(nameof(prix), "Le prix ne peut pas être négatif.");

Nom = nom;
Prix = prix;
Stock = stock;
}

// Constructeur simplifié : stock à 0 par défaut
public Produit(string nom, decimal prix) : this(nom, prix, 0) { }
}

Constructeurs primaires (C# 12)

C# 12 introduit les constructeurs primaires directement dans la déclaration de classe :

// Syntaxe traditionnelle
class Etudiant
{
public string Nom { get; }
public double Note { get; }

public Etudiant(string nom, double note)
{
Nom = nom; Note = note;
}
}

// Syntaxe constructeur primaire (C# 12) — plus concise
class Etudiant(string nom, double note)
{
public string Nom { get; } = nom;
public double Note { get; } = note;
}
Constructeurs primaires vs records

Les constructeurs primaires sont idéaux pour des classes simples. Si vos objets sont immuables (on ne modifie pas leurs valeurs après création), préférez les record (vus plus loin) qui génèrent automatiquement Equals, ToString et la déstructuration.

Validation dans le constructeur

Le constructeur est le bon endroit pour valider les données d'entrée — un objet invalide ne devrait jamais pouvoir être créé :

class Note
{
public double Valeur { get; }

public Note(double valeur)
{
if (valeur < 0 || valeur > 20)
throw new ArgumentOutOfRangeException(nameof(valeur),
$"Une note doit être entre 0 et 20, reçu : {valeur}");
Valeur = valeur;
}
}

// Impossible de créer une note invalide :
var n1 = new Note(15.5); // OK
var n2 = new Note(25.0); // ArgumentOutOfRangeException !

Exemple pratique

class Employe
{
private static int _prochainId = 1;

public int Id { get; }
public string Prenom { get; }
public string Nom { get; }
public string Poste { get; set; }
public decimal Salaire { get; private set; }

// Constructeur principal
public Employe(string prenom, string nom, string poste, decimal salaire)
{
if (string.IsNullOrWhiteSpace(prenom)) throw new ArgumentException("Prénom requis.", nameof(prenom));
if (string.IsNullOrWhiteSpace(nom)) throw new ArgumentException("Nom requis.", nameof(nom));
if (salaire < 0) throw new ArgumentOutOfRangeException(nameof(salaire), "Salaire négatif interdit.");

Id = _prochainId++;
Prenom = prenom;
Nom = nom;
Poste = poste;
Salaire = salaire;
}

// Constructeur délégué : poste "Stagiaire" avec salaire 0
public Employe(string prenom, string nom) : this(prenom, nom, "Stagiaire", 0m) { }

public void AugmenterSalaire(decimal pourcentage)
{
if (pourcentage <= 0) throw new ArgumentException("Le pourcentage doit être positif.");
Salaire *= (1 + pourcentage / 100);
}

public override string ToString()
=> $"#{Id:D3} {Prenom} {Nom}{Poste} ({Salaire:F0} €/mois)";
}

var e1 = new Employe("Alice", "Martin", "Développeuse", 3200m);
var e2 = new Employe("Bob", "Durand", "Chef de projet", 4100m);
var e3 = new Employe("Clara", "Petit"); // stagiaire

Console.WriteLine("=== Équipe ===");
Console.WriteLine(e1);
Console.WriteLine(e2);
Console.WriteLine(e3);

e1.AugmenterSalaire(5);
Console.WriteLine($"\nAprès augmentation de 5% pour Alice : {e1}");

Test de mémorisation/compréhension


Que se passe-t-il si vous définissez un constructeur avec paramètres et n'ajoutez pas de constructeur sans paramètres ?


Que fait `: this(nom, prix, 0)` dans un constructeur ?


Pourquoi valider les données dans le constructeur plutôt que dans les propriétés ?


Quelle syntaxe C# 12 permet de déclarer le constructeur directement dans la déclaration de classe ?


Peut-on surcharger les constructeurs (plusieurs constructeurs dans la même classe) ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer une classe Livre avec plusieurs constructeurs et validation.

Étape 1 — Constructeur principal avec validation

Créez la classe avec un constructeur qui valide les données.


Bonne pratique - ArgumentException vs ArgumentOutOfRangeException

ArgumentException signale qu'un argument est invalide (titre vide). ArgumentOutOfRangeException est plus précis : il s'utilise quand l'argument est hors d'une plage attendue (prix négatif, index invalide). Utiliser l'exception la plus spécifique aide l'appelant à comprendre exactement quel problème s'est produit.

Étape 2 — Constructeur délégué

Ajoutez un constructeur simplifié qui délègue au constructeur principal.


Bonne pratique - Déléguer plutôt que dupliquer

Utiliser : this(...) garantit que toute la logique de validation est centralisée dans le constructeur principal. Si vous dupliquez le code dans chaque constructeur et corrigez un bug dans l'un, vous pouvez oublier de corriger les autres. La délégation évite ce problème.

Étape 3 — Tester les constructeurs et la validation

Créez des livres avec différents constructeurs et testez la validation.


Bonne pratique - Tester les cas invalides

Un constructeur qui valide ses données doit être testé avec des entrées invalides pour s'assurer que les exceptions sont bien levées. En production, cela garantit qu'aucun objet "cassé" ne peut être créé. En développement, cela aide à comprendre les contrats d'utilisation de la classe.

📌 Une solution