CodeGym /Cours /C# SELF /Introduction à l'héritage

Introduction à l'héritage

C# SELF
Niveau 12 , Leçon 2
Disponible

1. Introduction

Le sujet d’aujourd’hui peut sembler un peu abstrait, mais crois-moi, tu ne pourras pas t’en passer plus tard. On va parler de l’héritage en programmation. Aujourd’hui, on va juste l’effleurer, et plus tard on rentrera dans les détails.

Imagine que tu codes un programme pour gérer les animaux d’un zoo. T’as différents types d’animaux : lions, tigres, éléphants, perroquets. Tous sont des animaux. Mais chacun a ses particularités :

  • Le lion a une crinière.
  • Le tigre est rayé.
  • L’éléphant est super grand et il a une trompe.
  • Le perroquet sait parler.

Si on décrivait chaque animal séparément, le code serait quasi identique :


// Lion
public class Lion
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "Lion"; // Toujours "Lion"
    public void Eat() { Console.WriteLine("Lion mange de la viande."); }
    public void Sleep() { Console.WriteLine("Lion dort."); }
    public void Roar() { Console.WriteLine("Lion rugit !"); }
    public string ManeColor { get; set; } // Spécificité du lion - crinière
}

// Tigre
public class Tiger
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Species { get; set; } = "Tigre"; // Toujours "Tigre"
    public void Eat() { Console.WriteLine("Tigre mange de la viande."); }
    public void Sleep() { Console.WriteLine("Tigre dort."); }
    public void Stride() { Console.WriteLine("Tigre se faufile."); }
    public string StripePattern { get; set; } // Spécificité du tigre - rayures
}

Tu vois tout ce code dupliqué ? Name, Age, Eat(), Sleep() — c’est commun à tous les animaux ! Et si on a 100 espèces ? L’horreur !

C’est là que l’héritage vient à la rescousse.

2. C’est quoi l’héritage de classes en C# ?

L’héritage — c’est un des principes fondamentaux de la programmation orientée objet (POO). Ça permet de créer de nouvelles classes à partir de classes existantes. La nouvelle classe (héritière, classe fille, classe dérivée) récupère (hérite) toutes les propriétés (champs) et comportements (méthodes) de sa classe parente (classe de base, classe mère).

C’est comme dans la vraie vie : tu hérites de certains traits de tes parents, mais t’as aussi tes propres particularités.

En C#, l’héritage se note avec les deux-points : après le nom de la classe fille :


            // Classe parente (de base) - Animal
public class Animal
{
    public string Name { get; set; } // Nom de l’animal
    public int Age { get; set; }     // Âge de l’animal

    public void Eat()
    {
        Console.WriteLine($"{Name} mange.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name} dort.");
    }
}

// Classe fille (dérivée) - Lion hérite de Animal
public class Lion : Animal // <-- Voilà, l’héritage !
{
    public string ManeColor { get; set; } // Propriété unique du lion

    public void Roar() // Comportement unique du lion
    {
        Console.WriteLine($"{Name} rugit : RRRRRR !");
    }
}

// Encore une classe fille - Tiger hérite de Animal
public class Tiger : Animal
{
    public string StripePattern { get; set; } // Propriété unique du tigre

    public void Stride() // Comportement unique du tigre
    {
        Console.WriteLine($"{Name} se faufile silencieusement.");
    }
}

Maintenant, si tu crées un objet Lion ou Tiger, ils auront automatiquement les propriétés Name et Age, ainsi que les méthodes Eat() et Sleep(), parce qu’ils les héritent de Animal.


class Program
{
    static void Main(string[] args)
    {
        Lion simba = new Lion();
        simba.Name = "Simba";
        simba.Age = 5;
        simba.ManeColor = "Doré";

        simba.Eat();   // Méthode héritée de Animal
        simba.Sleep(); // Méthode héritée de Animal
        simba.Roar();  // Méthode propre à Lion

        Console.WriteLine($"{simba.Name} - âge : {simba.Age}, couleur de crinière : {simba.ManeColor}");

        Tiger shereKhan = new Tiger();
        shereKhan.Name = "Shere Khan";
        shereKhan.Age = 7;
        shereKhan.StripePattern = "Classique";

        shereKhan.Eat();   // Méthode héritée de Animal
        shereKhan.Sleep(); // Méthode héritée de Animal
        shereKhan.Stride(); // Méthode propre à Tiger

        Console.WriteLine($"{shereKhan.Name} - âge : {shereKhan.Age}, motif : {shereKhan.StripePattern}");
    }
}

Idées clés de l’héritage :

  1. Réutilisation du code (Code Reusability) : Tu évites de répéter le code en mettant la logique commune dans la classe de base.
  2. Hiérarchie (Hierarchy) : Tu crées une structure logique "général-particulier" (AnimalLion/Tiger).
  3. Polymorphisme (Polymorphism) : (On fait juste mention, on verra plus tard) Tu peux bosser avec des objets des classes filles en utilisant des références de leur classe de base. Par exemple, avoir une liste Animal et y mettre des lions, des tigres, puis appeler leur méthode Eat().

3. Appeler les constructeurs des classes de base (base)

Quand tu crées un objet d’une classe fille (genre new Lion()), C# appelle automatiquement le constructeur de la classe de base (ici Animal) avant d’appeler celui de la classe fille. Ça garantit que la partie de base de l’objet est bien initialisée.

Parfois, tu dois passer des paramètres au constructeur de la classe de base. Pour ça, tu utilises le mot-clé base après la signature du constructeur de la classe fille.


public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }

    // Constructeur de la classe de base
    public Animal(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"Constructeur Animal : Animal créé {Name}, âge {Age}.");
    }

    public void Eat()
    {
        Console.WriteLine($"{Name} mange.");
    }

    public void Sleep()
    {
        Console.WriteLine($"{Name} dort.");
    }
}

public class Lion : Animal
{
    public string ManeColor { get; set; }

    // Constructeur Lion qui appelle le constructeur de la classe de base Animal
    public Lion(string name, int age, string maneColor) : base(name, age) // <-- Voilà, 'base' !
    {
        ManeColor = maneColor;
        Console.WriteLine($"Constructeur Lion : Couleur de crinière {ManeColor}.");
    }

    public void Roar()
    {
        Console.WriteLine($"{Name} rugit : RRRRRR !");
    }
}

            class Program
{
    static void Main(string[] args)
    {
        // Quand on crée Lion, d’abord le constructeur Animal, puis Lion    
        Lion simba = new Lion("Simba", 5, "Doré");
        simba.Roar();
    }
}

Ce qui se passe quand tu fais new Lion("Simba", 5, "Doré") :

  1. Le constructeur Lion(string name, int age, string maneColor) est appelé.
  2. Grâce à : base(name, age), le contrôle passe au constructeur Animal(string name, int age).
  3. Le code du constructeur Animal s’exécute, et tu vois Constructeur Animal : Animal créé Simba, âge 5.
  4. Ensuite, on revient dans le constructeur Lion.
  5. Le code du constructeur Lion s’exécute, et tu vois Constructeur Lion : Couleur de crinière Doré.

C’est super important : la classe de base est toujours initialisée en premier !

4. Les opérateurs is et as

Dans le monde du dev en C#, tu vas souvent devoir vérifier le type d’un objet ou essayer de le caster vers un autre type. C’est là que les opérateurs is et as sont super utiles. Ils servent surtout quand tu bosses avec l’héritage et le polymorphisme (on verra ça plus tard), mais tu peux déjà piger les bases.

Opérateur is (vérif de type)

L’opérateur is permet de checker si un objet est compatible avec un certain type. Il renvoie true si l’objet peut être casté vers ce type, et false sinon.

Syntaxe : expression is Type


static void AnalyzeObject(object obj)
{
    if (obj is string)
    {
        Console.WriteLine("C’est une chaîne !");
    }
    else if (obj is int)
    {
        Console.WriteLine("C’est un entier !");
    }
    else
    {
        Console.WriteLine("C’est autre chose.");
    }
}

static void Main()
{
    AnalyzeObject("Salut, monde !");      // C’est une chaîne !
    AnalyzeObject(123);                   // C’est un entier !
    AnalyzeObject(3.14);                  // C’est autre chose.
    AnalyzeObject(new int[] { 1, 2 });    // C’est autre chose.
}

is avec pattern matching :

Les versions récentes de C# permettent d’utiliser is non seulement pour vérifier le type, mais aussi pour créer direct une variable de ce type si ça matche. On appelle ça le "pattern matching" et ça rend le code bien plus concis.


static void AnalyzeObjectWithPatternMatching(object obj)
{
    if (obj is string message) // Si obj est une chaîne, on la met dans message
    {
        Console.WriteLine($"C’est une chaîne, sa longueur : {message.Length}");
    }
    else if (obj is int number) // Si obj est un int, on le met dans number
    {
        Console.WriteLine($"C’est un nombre, multiplié par 2 : {number * 2}");
    }
    else
    {
        Console.WriteLine("Type non reconnu.");
    }
}

static void Main()
{
    AnalyzeObjectWithPatternMatching("Exemple"); // C’est une chaîne, sa longueur : 7
    AnalyzeObjectWithPatternMatching(50);        // C’est un nombre, multiplié par 2 : 100
    AnalyzeObjectWithPatternMatching(new object()); // Type non reconnu.
}

Cette approche avec is et le pattern matching est bien mieux que le cast classique suivi d’un check sur null, car c’est plus safe et plus lisible.

Opérateur as (cast safe)

L’opérateur as essaie de caster un objet vers un type donné. Si ça marche, il renvoie l’objet casté. Si ça ne marche pas (l’objet n’est pas compatible), as renvoie null au lieu de balancer une exception. C’est pour ça que as est un opérateur de cast "safe".

Syntaxe : expression as Type


static void ProcessData(object data)
{
    string text = data as string; // On tente de caster data en string
    
    if (text != null) // On check si le cast a marché
    {
        Console.WriteLine($"Traitement de la chaîne : {text.ToUpper()}");
    }
    else
    {
        Console.WriteLine("Les données ne sont pas une chaîne.");
    }
}

static void Main()
{
    ProcessData("hello");      // Traitement de la chaîne : HELLO
    ProcessData(123);          // Les données ne sont pas une chaîne.
    ProcessData(null);         // Les données ne sont pas une chaîne.
}

Quand utiliser as au lieu du cast direct (Type)expression :

  • Sécurité : Si t’es pas sûr que l’objet est du bon type, as t’évite un InvalidCastException et te donne null à gérer.
  • Avec les types référence : as marche seulement avec les types référence (classes, interfaces, delegates, tableaux). Tu peux pas l’utiliser pour caster des types valeur (genre int vers double).

Important : Si tu es sûr que l’objet sera toujours du bon type, ou si tu dois caster un type valeur, utilise le cast direct (Type)expression. Si le cast échoue, le cast direct va balancer une erreur (InvalidCastException), ce qui peut être ce que tu veux si un mauvais type est une vraie erreur de logique dans ton code.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION