Les modificateurs d'accès
Notions théoriques
Les 4 modificateurs d'accès principaux
En C#, chaque membre (champ, propriété, méthode, classe) a un modificateur d'accès qui contrôle qui peut y accéder.
| Modificateur | Accessible depuis |
|---|---|
public | Partout — aucune restriction |
private | Dans la classe uniquement |
protected | Dans la classe et ses sous-classes |
internal | Dans le même projet (assembly) |
class Compte
{
public string Titulaire { get; } // accessible partout
private decimal _solde; // accessible seulement dans Compte
protected string _devise = "EUR"; // accessible dans Compte et sous-classes
public decimal Solde => _solde;
private void AuditLog(string msg) { }
public void Deposer(decimal m) { _solde += m; AuditLog("dépôt"); }
}
public — interface de la classe
Les membres public forment l'interface de la classe : ce que le monde extérieur peut utiliser. Réfléchissez bien à ce que vous rendez public — il est difficile de retirer un membre public sans casser le code existant.
class Produit
{
public string Nom { get; set; } = "";
public decimal Prix { get; set; }
public decimal PrixTTC(double tva = 0.20)
=> Prix * (decimal)(1 + tva);
}
var p = new Produit { Nom = "Clavier", Prix = 49.99m };
Console.WriteLine(p.PrixTTC());
private — détails d'implémentation
Les membres private sont des détails internes cachés. On peut les modifier sans affecter le code qui utilise la classe.
class Panier
{
private readonly List<string> _articles = [];
private decimal _total = 0;
public int NombreArticles => _articles.Count;
public decimal Total => _total;
public void AjouterArticle(string nom, decimal prix)
{
_articles.Add(nom);
_total += prix;
JournaliserAjout(nom);
}
private void JournaliserAjout(string nom)
=> Console.WriteLine($"[LOG] Ajouté : {nom}");
}
protected — héritage contrôlé
protected est utilisé pour les membres que les sous-classes peuvent utiliser mais que le monde extérieur ne peut pas voir :
class Forme
{
protected double _largeur;
protected double _hauteur;
public Forme(double largeur, double hauteur)
{
_largeur = largeur;
_hauteur = hauteur;
}
public virtual double Aire() => 0;
}
class Rectangle : Forme
{
public Rectangle(double l, double h) : base(l, h) { }
public override double Aire() => _largeur * _hauteur; // accès à _largeur OK
}
internal — visibilité limitée au projet
internal est accessible dans le même projet (assembly) mais pas depuis les projets qui utilisent cette bibliothèque. Utile pour les classes d'implémentation interne.
internal class ServiceInterne // utilisable dans le projet, pas depuis l'extérieur
{
public void FaireQuelqueChose() { }
}
Valeur par défaut
Sans modificateur explicite :
- Les membres d'une classe sont
privatepar défaut - Les classes de niveau supérieur sont
internalpar défaut
Principe du moindre privilège
Commencez par private. Passez à protected seulement si une sous-classe en a besoin. Passez à public seulement si l'utilisateur de la classe doit y accéder. Cette discipline limite les risques d'usage incorrect et facilite les évolutions futures.
Exemple pratique
class CompteBancaire
{
private decimal _solde;
private readonly List<string> _historique = [];
public string Titulaire { get; }
public decimal Solde => _solde;
public CompteBancaire(string titulaire, decimal soldeInitial = 0)
{
Titulaire = titulaire;
_solde = soldeInitial;
EnregistrerOperation($"Ouverture du compte avec {soldeInitial:F2} €");
}
public void Deposer(decimal montant)
{
ValiderMontant(montant);
_solde += montant;
EnregistrerOperation($"Dépôt : +{montant:F2} €");
}
public bool Retirer(decimal montant)
{
if (montant <= 0 || montant > _solde) return false;
_solde -= montant;
EnregistrerOperation($"Retrait : -{montant:F2} €");
return true;
}
public void AfficherHistorique()
{
Console.WriteLine($"Historique de {Titulaire} :");
foreach (string op in _historique)
Console.WriteLine($" {op}");
}
private void ValiderMontant(decimal montant)
{
if (montant <= 0)
throw new ArgumentException("Le montant doit être positif.");
}
private void EnregistrerOperation(string description)
=> _historique.Add($"[{DateTime.Now:HH:mm:ss}] {description}");
}
var compte = new CompteBancaire("Alice", 500m);
compte.Deposer(200m);
compte.Retirer(100m);
compte.AfficherHistorique();
// compte._solde = 9999; // ERREUR : private !
Console.WriteLine($"Solde : {compte.Solde:F2} €");
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez créer un système de bibliothèque avec encapsulation stricte.
Étape 1 — Concevoir la classe Livre avec la bonne visibilité
private set pour la modification contrôléepublic bool EstDisponible { get; private set; } expose la propriété en lecture depuis l'extérieur mais ne permet la modification que depuis l'intérieur de la classe. C'est préférable à exposer un champ public qui peut être modifié n'importe où.
Étape 2 — Méthodes publiques contrôlant l'état
_nbEmprunts est private : on ne peut pas l'incrémenter depuis l'extérieur. La seule façon de l'augmenter est d'appeler Emprunter(), qui applique aussi les règles métier. C'est l'essence de l'encapsulation : l'objet contrôle son propre état.
Étape 3 — Tester les accès
Si l'IDE ou le compilateur rejette livre._nbEmprunts = 0 avec "inaccessible en raison de son niveau de protection", c'est exactement le comportement attendu. L'encapsulation fonctionne : l'état interne est protégé et ne peut évoluer qu'à travers les méthodes publiques de la classe.