Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
LangagesIntermédiaireSérie : C# / .NET

C# : Les classes abstraites

Classes abstraites, méthodes abstraites vs virtuelles, sealed - quand et comment utiliser l'abstraction en C#.

5 min de lecture
Mis à jour il y a 4 mois
c#csharpabstractclasse abstraitesealedPOOhéritage

C# : Les classes abstraites

Dans l'article sur les objets, on a vu l'héritage avec virtual et override. Mais parfois, une classe parent n'a pas de sens toute seule - elle existe uniquement pour être héritée. C'est le rôle des classes abstraites.


# Qu'est-ce qu'une classe abstraite ?

Une classe abstraite est une classe qu'on ne peut pas instancier directement. Elle sert de base commune pour d'autres classes, en fournissant du code partagé et en imposant certaines méthodes que les enfants doivent implémenter.

abstract class Forme
{
    public string Couleur { get; set; }
 
    // Méthode abstraite : pas de corps, les enfants DOIVENT l'implémenter
    public abstract double CalculerAire();
 
    // Méthode concrète : du code partagé par tous les enfants
    public void Afficher()
    {
        Console.WriteLine($"Forme {Couleur}, aire = {CalculerAire()}");
    }
}
Forme f = new Forme();  // Erreur de compilation ! Impossible d'instancier une classe abstraite

# Implémenter une classe abstraite

Les classes enfants héritent du code concret et doivent implémenter les méthodes abstraites :

class Cercle : Forme
{
    public double Rayon { get; }
 
    public Cercle(double rayon, string couleur)
    {
        Rayon = rayon;
        Couleur = couleur;
    }
 
    public override double CalculerAire()
    {
        return Math.PI * Rayon * Rayon;
    }
}
 
class Rectangle : Forme
{
    public double Largeur { get; }
    public double Hauteur { get; }
 
    public Rectangle(double largeur, double hauteur, string couleur)
    {
        Largeur = largeur;
        Hauteur = hauteur;
        Couleur = couleur;
    }
 
    public override double CalculerAire()
    {
        return Largeur * Hauteur;
    }
}

Les deux classes héritent de Afficher() gratuitement, mais chacune fournit sa propre implémentation de CalculerAire(). Si un enfant oublie d'implémenter une méthode abstraite, le compilateur refuse de compiler.

Cercle c = new Cercle(5, "Rouge");
c.Afficher();  // "Forme Rouge, aire = 78.54..."
 
Rectangle r = new Rectangle(4, 3, "Bleu");
r.Afficher();  // "Forme Bleu, aire = 12"

# abstract vs virtual

Les deux permettent aux enfants de redéfinir un comportement, mais avec une différence clé :

abstractvirtual
Corps dans le parentNon (interdit)Oui (obligatoire)
L'enfant doit redéfinirOui (obligatoire)Non (optionnel)
Le parent peut être instanciéNonOui
abstract class Animal
{
    // Abstrait : chaque animal DOIT définir son cri
    public abstract string Crier();
 
    // Virtuel : comportement par défaut, redéfinissable
    public virtual string Decrire()
    {
        return $"Je suis un animal qui fait : {Crier()}";
    }
}
 
class Chat : Animal
{
    public override string Crier() => "Miaou";
    // Decrire() est hérité tel quel  -  pas obligé de le redéfinir
}
 
class Chien : Animal
{
    public override string Crier() => "Wouf";
 
    public override string Decrire()
    {
        return $"Je suis un bon chien ! {Crier()} !";
    }
}

En résumé : abstract = "tu n'as pas le choix, implémente-le". virtual = "j'ai une version par défaut, mais tu peux la changer".


# Le mot-clé sealed

sealed est l'opposé de abstract : il interdit l'héritage. Une classe sealed ne peut pas avoir d'enfants.

sealed class Configuration
{
    public string ConnectionString { get; init; }
    public int Timeout { get; init; }
}
 
// class MaConfig : Configuration { }  // Erreur ! Configuration est sealed

On peut aussi seal une méthode spécifique dans une chaîne d'héritage pour empêcher les enfants de la redéfinir plus loin :

class Animal
{
    public virtual string Crier() => "...";
}
 
class Chat : Animal
{
    public sealed override string Crier() => "Miaou";
}
 
class ChatSauvage : Chat
{
    // public override string Crier() => "MIAOU !!!";  // Erreur ! Crier est sealed dans Chat
}

Quand utiliser sealed ?

  • Quand une classe n'est pas conçue pour être héritée (la majorité des classes)
  • Pour des raisons de performance : le compilateur peut optimiser les appels de méthodes sur les classes sealed (pas de vtable lookup)
  • Par sécurité : empêcher un code tiers de modifier le comportement en héritant

En pratique, beaucoup de développeurs C# recommandent de mettre sealed par défaut sur toutes les classes et de ne l'enlever que quand l'héritage est explicitement voulu. C'est le même principe que private par défaut : on ferme d'abord, on ouvre si besoin.


# Classe abstraite vs interface

C'est la question classique. Le tableau complet est dans l'article sur les interfaces, mais voici la règle simple :

  • Classe abstraite -> tu veux partager du code entre les enfants (champs, constructeurs, méthodes concrètes)
  • Interface -> tu veux définir un contrat que des classes sans lien de parenté peuvent respecter

Et on peut combiner les deux :

interface IAffichable
{
    void Afficher();
}
 
abstract class Forme : IAffichable
{
    public string Couleur { get; set; }
    public abstract double CalculerAire();
 
    public void Afficher()
    {
        Console.WriteLine($"{Couleur}, aire = {CalculerAire()}");
    }
}

Forme est abstraite (on ne peut pas l'instancier) ET implémente IAffichable (elle respecte le contrat). Les enfants héritent les deux.


# La suite