Chapitre 4 – Les propriétés C#

Les propriétés sont la version moderne et concise des getters/setters du Chapitre 3. Elles se comportent comme des attributs public pour le code appelant, mais elles cachent à l'intérieur tout le contrôle qu'on souhaite. C# en fait un usage si systématique qu'on ne voit presque jamais de GetX() / SetX() dans un vrai projet C#.

    5ttr 6ttr
  • Découverte

Tu as vu au Chapitre 3 que les getters/setters fonctionnent mais qu'ils sont lourds : 8 méthodes pour 4 attributs, et le code appelant écrit hero.GetPv() partout. C# propose une syntaxe dédiée — la propriété — qui fait exactement la même chose, en deux fois moins de lignes, avec un usage transparent.


🎯 Objectifs

À la fin de ce chapitre, tu seras capable de :

  1. Écrire une propriété avec ses blocs get et set.
  2. Utiliser le mot-clé value dans un set.
  3. Choisir entre la forme complète (avec validation) et la forme raccourcie ({ get; set; }).
  4. Limiter l'écriture avec private set (lecture publique, écriture interne).
  5. Écrire une propriété calculée (un get sans set).
  6. Encapsuler une liste interne dans une classe.

Partie 1 — Du getter/setter à la propriété

Reprends le getter/setter de Pv du chapitre précédent :

class Personnage
{
    private int pv;

    public int GetPv()
    {
        return pv;
    }

    public void SetPv(int valeur)
    {
        if (valeur < 0)     valeur = 0;
        if (valeur > pvMax) valeur = pvMax;
        pv = valeur;
    }
}

Voici la même logique, en C#, sous forme de propriété :

class Personnage
{
    private int pv;

    public int Pv
    {
        get
        {
            return pv;
        }
        set
        {
            if (value < 0)     value = 0;
            if (value > pvMax) value = pvMax;
            pv = value;
        }
    }
}

Trois choses à remarquer :

  • La propriété s'écrit comme un attribut public (public int Pv) suivi d'accolades qui contiennent deux blocs : get et set.
  • À l'intérieur du set, le mot-clé value désigne la valeur qu'on essaie d'assigner. C'est l'équivalent du paramètre du setter classique.
  • L'attribut privé pv (minuscule) est conservé : c'est lui qui stocke réellement la donnée. La propriété Pv (majuscule) n'est qu'une interface qui l'expose proprement.

🧠 Convention : attribut privé en camelCase (pv), propriété publique en PascalCase (Pv). C'est la convention C# universelle, et c'est précisément pour distinguer ces deux compagnons qu'elle existe.


Partie 2 — L'usage côté Program.cs

C'est là que la propriété fait toute la différence. Du point de vue de celui qui utilise la classe, une propriété s'utilise comme un attribut :

Personnage hero = new Personnage();

hero.Pv = 100;       // appelle implicitement le set → stocke 100
hero.Pv = -9999;     // appelle implicitement le set → stocke 0 (validation)
hero.Pv = 50000;     // appelle implicitement le set → stocke pvMax (validation)

int actuels = hero.Pv;   // appelle implicitement le get → renvoie la valeur
Console.WriteLine(hero.Pv);

Pas de Get, pas de Set, pas de parenthèses. La protection est invisible de l'extérieur. L'écriture est aussi simple qu'avec un attribut public — la sécurité est aussi solide qu'avec un setter classique.


Partie 3 — La forme raccourcie

Très souvent, on n'a aucune validation à faire : on veut juste un attribut accessible en lecture et en écriture. Pour ce cas, C# propose une forme raccourcie : la propriété auto-implémentée.

public string Nom { get; set; }

Une seule ligne. Plus d'attribut privé à déclarer — C# en crée un automatiquement, en coulisses. Cette ligne est exactement équivalente à :

private string _nom;
public string Nom
{
    get { return _nom; }
    set { _nom = value; }
}

⚠️ Mais attention : la forme raccourcie ne permet aucune validation. Dès que tu as besoin de refuser ou corriger une valeur, passe à la forme complète.

Variantes utiles

public string Classe { get; set; }              // lecture + écriture publiques
public int    Identifiant { get; private set; } // lecture publique, écriture interne uniquement
public bool   EstActif { get; }                 // lecture seule (rare, voir propriétés calculées)

La version avec private set est très utile : depuis l'extérieur, on ne peut que lire (par exemple hero.Identifiant), mais à l'intérieur de la classe, on peut écrire (par exemple à la création de l'objet).


Partie 4 — Les propriétés calculées

Une propriété calculée est une propriété qui n'a pas d'attribut privé dédié. Sa valeur est calculée à la volée à partir d'autres attributs. Elle n'a typiquement pas de set — on ne peut pas écrire dedans, seulement lire.

public bool EstVivant
{
    get { return pv > 0; }
}

Aucun attribut privé estVivant ; à chaque appel, on évalue l'expression pv > 0.

hero.Pv = 0;
Console.WriteLine(hero.EstVivant);   // False

hero.Pv = 50;
Console.WriteLine(hero.EstVivant);   // True

🧠 Personne ne peut écrire hero.EstVivant = true : sans set, la propriété est en lecture seule. La seule manière de changer EstVivant, c'est de modifier Pv — ce qui est exactement ce qu'on veut. La cohérence est garantie par construction.

D'autres exemples utiles

// Pourcentage de PV restants
public int PourcentagePv
{
    get { return (int)((double)pv / pvMax * 100); }
}

// Barre de vie textuelle : "[████████░░]"
public string BarreDeVie
{
    get
    {
        int nb = PourcentagePv / 10;
        return "[" + new string('█', nb) + new string('░', 10 - nb) + "]";
    }
}

Syntaxe encore plus courte : =>

Pour les propriétés calculées d'une seule ligne, C# accepte une forme avec flèche :

public bool EstVivant => pv > 0;
public int  PourcentagePv => (int)((double)pv / pvMax * 100);

C'est strictement équivalent au bloc get { return ...; }, en beaucoup plus court.


Partie 5 — La classe Personnage au complet

Voici à quoi ressemble notre classe Personnage, encapsulée avec toutes les techniques vues ci-dessus :

class Personnage
{
    // --- Attributs privés ---
    private string nom;
    private int    pv;
    private int    pvMax;
    private int    niveau;

    // --- Propriété avec validation : Nom ---
    public string Nom
    {
        get { return nom; }
        set
        {
            if (value == "" || value == null) value = "Inconnu";
            nom = value;
        }
    }

    // --- Propriété avec validation : Pv (bornée entre 0 et PvMax) ---
    public int Pv
    {
        get { return pv; }
        set
        {
            if (value < 0)     value = 0;
            if (value > pvMax) value = pvMax;
            pv = value;
        }
    }

    // --- Propriété avec validation : PvMax (minimum 1) ---
    public int PvMax
    {
        get { return pvMax; }
        set
        {
            if (value < 1) value = 1;
            pvMax = value;
        }
    }

    // --- Propriété auto-implémentée : Classe ---
    public string Classe { get; set; }

    // --- Propriété avec validation : Niveau (minimum 1) ---
    public int Niveau
    {
        get { return niveau; }
        set
        {
            if (value < 1) value = 1;
            niveau = value;
        }
    }

    // --- Propriété calculée : EstVivant ---
    public bool EstVivant => pv > 0;

    // --- Propriété calculée : barre de vie textuelle ---
    public string BarreDeVie
    {
        get
        {
            int nb = pvMax == 0 ? 0 : pv * 10 / pvMax;
            return "[" + new string('█', nb) + new string('░', 10 - nb) + "]";
        }
    }
}

Test dans Program.cs

Personnage hero = new Personnage();
hero.Nom    = "Aragorn";
hero.Classe = "Guerrier";
hero.PvMax  = 100;
hero.Pv     = 100;
hero.Niveau = 5;

Console.WriteLine($"{hero.Nom} ({hero.Classe}) — Niv.{hero.Niveau}");
Console.WriteLine($"PV : {hero.Pv}/{hero.PvMax} {hero.BarreDeVie}");
// Aragorn (Guerrier) — Niv.5
// PV : 100/100 [██████████]

hero.Pv = -9999;          // ramené à 0
Console.WriteLine(hero.BarreDeVie);
// [░░░░░░░░░░]
Console.WriteLine(hero.EstVivant);   // False

Compare le nombre de lignes avec le getter/setter du Chapitre 3 — et surtout compare la lisibilité du code appelant.


Partie 6 — Livrable : la classe Inventaire

Voici un exemple complet qui combine propriétés calculées et liste interne protégée.

La classe Inventaire contient une liste d'objets appartenant à un personnage. La liste est private : aucun code à l'extérieur ne peut la modifier directement. On passe obligatoirement par les méthodes prévues.

class Inventaire
{
    // La liste est privée et initialisée à la déclaration
    // (pas besoin de constructeur — on verra cette syntaxe au Chap. 6)
    private List<string> objets = new List<string>();

    // Capacité maximale (auto-implémentée, modifiable de l'extérieur)
    public int CapaciteMax { get; set; } = 10;

    // Propriété calculée : nombre d'objets dans l'inventaire
    public int Taille => objets.Count;

    // Propriété calculée : est-ce que l'inventaire est plein ?
    public bool EstPlein => objets.Count >= CapaciteMax;

    // --- Méthodes métier ---

    public void Ajouter(string objet)
    {
        if (EstPlein)
        {
            Console.WriteLine($"Inventaire plein ! Impossible d'ajouter {objet}");
            return;
        }
        objets.Add(objet);
        Console.WriteLine($"{objet} ajouté à l'inventaire.");
    }

    public void Retirer(string objet)
    {
        if (!objets.Contains(objet))
        {
            Console.WriteLine($"{objet} n'est pas dans l'inventaire.");
            return;
        }
        objets.Remove(objet);
        Console.WriteLine($"{objet} retiré de l'inventaire.");
    }

    public void Afficher()
    {
        if (Taille == 0)
        {
            Console.WriteLine("L'inventaire est vide.");
            return;
        }
        Console.WriteLine($"Inventaire ({Taille}/{CapaciteMax}) :");
        foreach (string o in objets)
        {
            Console.WriteLine($"  - {o}");
        }
    }
}

Utilisation

Inventaire sac = new Inventaire();

sac.Ajouter("Épée longue");
sac.Ajouter("Potion de soin");
sac.Ajouter("Carte du donjon");

sac.Afficher();
// Inventaire (3/10) :
//   - Épée longue
//   - Potion de soin
//   - Carte du donjon

sac.Retirer("Potion de soin");
sac.Retirer("Arc elfique");   // n'existe pas → message d'erreur

Console.WriteLine($"Objets restants : {sac.Taille}");
// Objets restants : 2

💡 Observe que Taille, EstPlein et CapaciteMax sont des propriétés, alors que Ajouter, Retirer et Afficher sont des méthodes. La règle pour choisir : propriété pour exposer une donnée (lecture, écriture, calcul) ; méthode pour exécuter une action. On formalisera tout ça au Chapitre 5.


Partie 7 — Schéma récapitulatif

Voici comment les trois chapitres s'enchaînent :

CHAP. 1                CHAP. 2                  CHAP. 3                CHAP. 4
──────────────         ──────────────           ──────────────         ──────────────
public int Pv;     →   private int pv;      →   private int pv;     →  private int pv;

                                                public int GetPv()      public int Pv
                                                { return pv; }          {
                                                                            get { return pv; }
                                                public void SetPv(int v)    set
                                                {                           {
                                                    if (v < 0) v = 0;           if (value < 0)
                                                    pv = v;                         value = 0;
                                                }                               pv = value;
                                                                            }
                                                                        }

hero.Pv = -9999;       ❌ inaccessible          hero.SetPv(-9999);     hero.Pv = -9999;
(accepté, dégâts)      (compile error)         (stocke 0 silenc.)     (stocke 0 silenc.)

🧪 Exercices

Exercice 1 — Refondre CompteBancaire

Reprends ta classe CompteBancaire du Chapitre 3 (ex. 2). Convertis tes getters/setters en propriétés.

  • Titulaire : propriété avec validation (refuser une chaîne vide).
  • Solde : propriété en lecture seule depuis l'extérieur (public double Solde { get; private set; }). L'écriture passe uniquement par Deposer et Retirer.

Test : essaie compte.Solde = 1_000_000; depuis Program.cs — Rider doit refuser de compiler.

Exercice 2 — La lampe connectée

Crée une classe LampeConnectee avec :

  • Couleur (string, propriété auto-implémentée).
  • Intensite (int, propriété avec validation : bornée entre 0 et 100).
  • EstAllumee (bool, propriété auto-implémentée).
  • Description : propriété calculée qui renvoie une chaîne du genre : "Lampe rouge — intensité 80% — allumée" ou "Lampe blanche — éteinte" si la lampe n'est pas allumée.

Exercice 3 — Le produit en stock

Crée une classe Produit avec :

  • Nom, Prix, Quantite (propriétés avec validations adaptées : prix ≥ 0, quantité ≥ 0).
  • ValeurStock : propriété calculée = Prix × Quantite.
  • EnRupture : propriété calculée = true si la quantité est à 0.

Bonus : ajoute une méthode Vendre(int nombre) qui diminue la quantité de nombre unités, en refusant si le stock est insuffisant.


✍️ À retenir

  • Une propriété est la version C# du couple getter/setter, dans une syntaxe compacte.
  • Dans un set, le mot-clé value représente la valeur que l'appelant cherche à affecter.
  • Forme complète : pour valider, transformer, ou faire un calcul → blocs get { ... } / set { ... }.
  • Forme raccourcie : { get; set; } pour les propriétés sans validation — C# crée l'attribut privé tout seul.
  • private set = lecture publique, écriture interne uniquement.
  • Une propriété calculée n'a pas d'attribut privé : elle calcule sa valeur à chaque appel, et n'a pas de set.
  • Une propriété expose une donnée ; une méthode exécute une action. Garde la distinction claire.

Suite

On a vu les données d'une classe en profondeur : attributs privés, propriétés, propriétés calculées. Il reste l'autre moitié — les méthodes. On les a utilisées sans jamais les formaliser : type de retour, paramètres, le mot-clé this, la méthode magique ToString(). C'est l'objet du chapitre suivant.

Pour aller plus loin