DTeK Learn logo

Tout savoir sur les héritages C#

13/12/2023

L’héritage en Programmation Orientée Objet (POO) permet de créer des relations entre les classes. Une classe hérite des caractéristiques (méthodes et propriétés) d’une autre classe.

L’héritage est basé sur le principe « est-un », ce qui signifie que la classe dérivée est une version spécialisée de la classe de base. En héritant des propriétés et des comportements de la classe de base, la classe dérivée peut réutiliser le code déjà existant, évitant ainsi la duplication et favorisant une meilleure organisation du code.

jeune développeur en reconversion professionnelle sur son ordinateur à son bureau

1. Les fondamentaux de l’héritage

L’héritage en C# est un concept clé de la Programmation Orientée Objet (POO) qui permet de créer des relations entre les classes, favorisant ainsi la réutilisation du code et la création de hiérarchies. Dans cette section, nous allons explorer en détail les fondamentaux de l’héritage, y compris sa définition, son utilité et la relation « est-un » entre les classes.

 

1.1 Qu’est-ce que l’héritage en POO ?

L’héritage est un mécanisme fondamental de la POO qui permet à une classe de prendre les caractéristiques d’une autre classe.

La classe qui fournit les caractéristiques est appelée classe de base ou classe parent, tandis que la classe qui hérite de ces caractéristiques est appelée classe dérivée ou classe enfant.

L’héritage est basé sur le principe d’héritage « est-un ». Cela signifie que la classe dérivée est une version spécialisée de la classe de base et peut être considérée comme une instance plus spécifique de cette dernière.

Par exemple, considérons une classe de base « Animal » et une classe dérivée « Chien ». Un chien est un type spécifique d’animal, et donc la classe « Chien » peut hériter des propriétés et méthodes de la classe « Animal ».

 

1.2 Pourquoi utiliser l’héritage en C# ?

L’héritage offre plusieurs avantages qui améliorent la conception logicielle et facilitent la maintenance du code.

Réutilisation du code
En héritant des caractéristiques d’une classe de base, la classe dérivée peut éviter de réécrire le code existant, ce qui favorise la réutilisation et la cohérence du code.

Hiérarchie de classes
L’héritage permet de créer des hiérarchies de classes, où les classes dérivées peuvent être regroupées par rapport à la classe de base. Cela facilite l’organisation du code et la compréhension de la structure du programme.

Extensibilité
En ajoutant de nouvelles fonctionnalités dans une classe dérivée, tu peux étendre les fonctionnalités déjà présentes dans la classe de base sans affecter son code existant.

Polymorphisme
L’héritage facilite l’utilisation du polymorphisme, qui permet à une classe dérivée de remplacer ou d’étendre le comportement d’une méthode héritée de la classe de base.

 

1.3 La relation « est-un » entre les classes

La relation « est-un » entre les classes est le principe fondamental de l’héritage. Elle indique qu’une classe dérivée est une version plus spécifique de la classe de base.

Par exemple, dans notre exemple précédent, un chien « est-un » animal, ce qui signifie que la classe « Chien » peut hériter de la classe « Animal ». Pour établir cette relation, tu peux déclarer la classe dérivée en utilisant le mot-clé class suivi du nom de la classe dérivée et du nom de la classe de base qu’elle hérite, séparés par deux points :

public class Animal {
    // Propriétés et méthodes de la classe Animal
}

public class Chien : Animal {
    // Propriétés et méthodes spécifiques à la classe Chien
}

Ainsi, la classe « Chien » héritera de toutes les propriétés et méthodes définies dans la classe « Animal » et pourra également définir ses propres propriétés et méthodes spécifiques.

2. Créer des Classes Héritées

Dans cette section, nous allons explorer en détail la syntaxe de l’héritage en C# et comment créer des classes dérivées. Nous aborderons également les concepts de classes de base et de classes dérivées, ainsi que l’utilisation du mot-clé base pour appeler le constructeur parent.

 

2.1 La syntaxe de l’héritage en C#

L’héritage en C# est réalisé en utilisant le mot-clé class suivi du nom de la classe dérivée, suivi de : et du nom de la classe de base qu’elle hérite. Voici la syntaxe générale :

public class ClasseDeBase {
    // Propriétés et méthodes de la classe de base
}

public class ClasseDerivee : ClasseDeBase {
    // Propriétés et méthodes spécifiques à la classe dérivée
}

La classe dérivée héritera toutes les propriétés et méthodes publiques et protégées de la classe de base. Les membres privés de la classe de base ne sont pas accessibles dans la classe dérivée.

 

2.2 Les classes de base et les classes dérivées

La classe de base est la classe dont les caractéristiques sont héritées par la classe dérivée. La classe de base peut être considérée comme une classe générique qui définit les propriétés et méthodes communes à toutes ses classes dérivées.

La classe dérivée, quant à elle, est la classe qui hérite des propriétés et méthodes de la classe de base. Elle peut également définir ses propres propriétés et méthodes spécifiques qui lui sont propres.

 

2.3 L’utilisation du mot-clé base pour appeler le constructeur parent

Dans certaines situations, il peut être nécessaire d’appeler le constructeur de la classe de base depuis la classe dérivée. Cela se produit généralement lorsque tu souhaites initialiser des propriétés spécifiques de la classe de base avant d’initialiser celles de la classe dérivée.

Pour appeler le constructeur de la classe de base, tu peux utiliser le mot-clé base suivi des arguments requis pour le constructeur de la classe de base. Cela doit être fait dans le constructeur de la classe dérivée, à la première ligne du code.

public class ClasseDeBase {
    public ClasseDeBase(string nom) {
        // Initialisation de la classe de base
    }
}

public class ClasseDerivee : ClasseDeBase {
    public ClasseDerivee(string nom, int age) : base(nom) {
        // Initialisation de la classe dérivée
    }
}

Dans cet exemple, la classe dérivée ClasseDerivee appelle le constructeur de la classe de base ClasseDeBase en utilisant le mot-clé base(nom). Cela garantit que le constructeur de la classe de base est appelé avant que le constructeur de la classe dérivée ne soit exécuté.

3. Les Modificateurs d’Accès dans l’Héritage

Dans cette section, nous allons explorer les modificateurs d’accès en C# qui sont pertinents dans le contexte de l’héritage. Ces modificateurs déterminent la visibilité des membres hérités dans la classe dérivée. Nous aborderons les modificateurs d’accès public, protected, private et internal, ainsi que leur influence sur l’héritage et la conception des classes.

 

3.1 Comprendre les modificateurs d’accès public, protected, private et internal

En C#, les modificateurs d’accès sont utilisés pour définir l’accès aux membres (propriétés et méthodes) d’une classe. Voici une brève explication de chacun de ces modificateurs.

public :
Les membres publics sont accessibles à partir de n’importe quelle classe dans l’assemblage ou à partir d’autres assemblages qui référencent cet assemblage. Les membres publics sont hérités par les classes dérivées et sont accessibles depuis l’extérieur de la classe.

protected :
Les membres protégés sont accessibles à partir de la classe elle-même et de ses classes dérivées. Ils ne sont pas accessibles depuis l’extérieur de la classe. Les membres protégés sont souvent utilisés pour définir des comportements que les classes dérivées peuvent modifier ou étendre.

private :
Les membres privés sont accessibles uniquement à partir de la classe elle-même et ne sont pas hérités par les classes dérivées. Ils sont utilisés pour encapsuler des fonctionnalités internes de la classe et ne sont pas visibles à l’extérieur.

internal :
Les membres internes sont accessibles à partir de n’importe quelle classe dans le même assemblage, mais pas à partir des assemblages qui référencent cet assemblage. Les membres internes sont souvent utilisés pour fournir une accessibilité au niveau de l’assemblage, ce qui signifie que seules les classes dans le même assemblage peuvent y accéder.

 

3.2 Comment les modificateurs d’accès affectent l’héritage ?

Les modificateurs d’accès jouent un rôle crucial dans l’héritage en déterminant la visibilité des membres hérités dans la classe dérivée. Voici comment les modificateurs d’accès affectent l’héritage :

  1. Les membres publics de la classe de base sont hérités comme des membres publics dans la classe dérivée. Ils restent accessibles depuis l’extérieur de la classe dérivée.
  2. Les membres protégés de la classe de base sont hérités comme des membres protégés dans la classe dérivée. Ils peuvent être utilisés et redéfinis dans les classes dérivées, mais ne sont pas accessibles depuis l’extérieur de la classe dérivée.
  3. Les membres privés de la classe de base ne sont pas hérités par la classe dérivée et ne sont pas visibles à l’intérieur de celle-ci. Ils sont uniquement accessibles dans la classe de base elle-même.
  4. Les membres internes de la classe de base sont hérités comme des membres internes dans la classe dérivée. Ils restent accessibles uniquement à partir des classes dans le même assemblage que la classe dérivée.

 

3.3 Bonnes pratiques pour la conception des classes héritées

Lorsque tu conserves des classes héritées, il est essentiel de prendre en compte les modificateurs d’accès pour maintenir l’encapsulation et la sécurité du code. Voici quelques bonnes pratiques à suivre :

 

  1. Utilise le modificateur d’accès le plus restrictif possible pour les membres de la classe de base. Cela permet de mieux contrôler l’accès aux fonctionnalités internes de la classe et de minimiser les dépendances externes.
  2. Utilise le modificateur d’accès « protected » pour les membres que tu souhaites rendre accessibles aux classes dérivées, tout en maintenant l’encapsulation.
  3. Évite d’utiliser des membres publics, sauf si cela est nécessaire. Préfère plutôt les propriétés et méthodes publics qui exposent uniquement les fonctionnalités nécessaires.
  4. N’expose pas les membres internes de la classe de base à moins qu’il ne soit absolument nécessaire de le faire. Garde l’accessibilité au niveau de l’assemblage pour les fonctionnalités internes.

4. Les Méthodes et Propriétés Virtuelles

Dans cette section, nous allons approfondir les concepts de méthodes et de propriétés virtuelles en C#. Les méthodes et propriétés virtuelles sont des éléments clés de l’héritage, car ils permettent à une classe dérivée de redéfinir le comportement des membres hérités de la classe de base. Nous explorerons la différence entre surcharge et redéfinition, ainsi que l’utilisation du mot-clé « override » pour redéfinir les membres hérités.

 

4.1 Introduction aux méthodes et propriétés virtuelles

Les méthodes et propriétés virtuelles sont définies dans la classe de base avec le mot-clé virtual. Une méthode virtuelle est une méthode qui peut être redéfinie dans une classe dérivée, tandis qu’une propriété virtuelle est une propriété dont le comportement peut être redéfini. Pour déclarer une méthode virtuelle, utilise le mot-clé virtual avant le type de retour de la méthode :

public class ClasseDeBase {
    public virtual void MethodeVirtuelle() {
        // Implémentation de la méthode virtuelle
    }
}

Pour déclarer une propriété virtuelle, utilise le mot-clé virtual avant le type de la propriété :

public class ClasseDeBase {
    public virtual int MaProprieteVirtuelle {
        get;
        set;
    }
}

4.2 Surcharge vs Redéfinition

La surcharge (ou overriding en anglais) et la redéfinition (ou overloading en anglais) sont deux concepts importants en POO et peuvent être confondues. Voici la différence entre ces deux concepts.

Surcharge (Overriding) :
La surcharge se produit lorsque la classe dérivée fournit une implémentation différente d’une méthode héritée de la classe de base. Pour effectuer une surcharge, utilise le mot-clé override avant la déclaration de la méthode dans la classe dérivée :

public class ClasseDerivee : ClasseDeBase {
    public override void MethodeVirtuelle() {
        // Implémentation spécifique à la classe dérivée
    }
}

Redéfinition (Overloading) :
La redéfinition se produit lorsque la classe dérivée fournit une surcharge pour une méthode existante dans la classe de base. Cela signifie qu’il y a deux méthodes portant le même nom, mais ayant des signatures différentes. La redéfinition n’est pas liée à l’héritage et n’utilise pas le mot-clé override.

 

4.3 Utilisation du mot-clé override pour redéfinir les membres hérités

Pour redéfinir une méthode virtuelle dans une classe dérivée, utilise le mot-clé override avant la déclaration de la méthode. Cela indique au compilateur que la méthode dans la classe dérivée remplace la méthode virtuelle de la classe de base.

public class ClasseDeBase {
    public virtual void MethodeVirtuelle() {
        // Implémentation de la méthode virtuelle dans la classe de base
    }
}

public class ClasseDerivee : ClasseDeBase {
    public override void MethodeVirtuelle() {
        // Implémentation spécifique à la classe dérivée
    }
}

Lorsque tu appelles la méthode virtuelle à partir d’un objet de la classe dérivée, la version redéfinie dans la classe dérivée sera appelée, tandis que l’appel à la méthode sur un objet de la classe de base utilisera l’implémentation de la classe de base.

5. Le Polymorphisme et les Interfaces

Dans cette section, nous allons explorer le concept de polymorphisme en C# et comment il est associé à l’héritage. Nous aborderons également les interfaces, un autre mécanisme puissant en POO, qui permet de réaliser le polymorphisme en C#. Nous allons examiner comment utiliser les interfaces pour créer des contrats de comportement et permettre à une classe d’implémenter plusieurs interfaces.

 

5.1 Comprendre le polymorphisme en C#

Le polymorphisme est un concept central de la POO qui permet à une classe de se comporter de différentes manières en fonction de son type réel. Cela signifie qu’une classe peut être utilisée de manière interchangeable avec une autre classe ayant une relation d’héritage. Le polymorphisme en C# est rendu possible grâce à l’utilisation de méthodes virtuelles et du mot-clé override que nous avons vu dans la section précédente.

Lorsque tu appelles une méthode virtuelle sur un objet de classe dérivée, la méthode appropriée, redéfinie dans cette classe dérivée, sera appelée. Par exemple, considérons une hiérarchie de classes avec une classe de base « Forme » et deux classes dérivées « Cercle » et « Rectangle ». Chaque classe dérivée a sa propre implémentation de la méthode « CalculerAire » :

public class Forme {
    public virtual double CalculerAire() {
        return 0;
    }
}
public class Cercle : Forme {
    public override double CalculerAire() {
        // Calcul de l’aire spécifique au cercle
    }
}
public class Rectangle : Forme {
    public override double CalculerAire() {
        // Calcul de l’aire spécifique au rectangle
    }
}

Ainsi, lorsqu’on appelle la méthode CalculerAire sur un objet de type Forme, la méthode appropriée sera appelée en fonction du type réel de l’objet. Si l’objet est de type Cercle, la méthode CalculerAire définie dans la classe Cercle sera appelée, et si l’objet est de type Rectangle, la méthode CalculerAire définie dans la classe Rectangle sera appelée.

 

5.2 Utilisation des interfaces pour une flexibilité accrue

Les interfaces en C# fournissent un moyen supplémentaire d’atteindre le polymorphisme. Une interface est une liste de signatures de méthodes et de propriétés qui forment un contrat que les classes qui implémentent cette interface doivent respecter. Une classe peut implémenter plusieurs interfaces, ce qui lui permet de prendre en charge différents comportements. Pour définir une interface, utilisez le mot-clé interface suivi du nom de l’interface :

public interface IImprimable { void Imprimer(); }  

Pour qu’une classe implémente une interface, utilisez le mot-clé class suivi du nom de la classe et du nom de l’interface séparés par deux points :

public class Document : IImprimable {
    public void Imprimer() {
        // Implémentation spécifique à la classe Document
    }
}

Une classe peut implémenter plusieurs interfaces :

public class Photo : IImprimable, IExportable {
    public void Imprimer() {
        // Implémentation spécifique à la classe Photo pour l’impression
    }
    public void Exporter() {
        // Implémentation spécifique à la classe Photo pour l’exportation
    }
}

En utilisant des interfaces, tu peux définir des contrats de comportement pour les classes qui les implémentent, ce qui permet une plus grande flexibilité dans la conception du code et facilite le polymorphisme.

6. Les Classes Abstraites

Dans cette section, nous allons explorer les classes abstraites en C#. Une classe abstraite est une classe spéciale qui ne peut pas être instanciée directement, mais qui sert de modèle pour d’autres classes en définissant un ensemble de membres (méthodes et propriétés) que les classes dérivées doivent implémenter.

Nous aborderons la syntaxe pour définir une classe abstraite, la différence entre les méthodes abstraites et les méthodes concrètes, ainsi que l’utilisation des classes abstraites dans le cadre de l’héritage.

 

6.1 Définition et syntaxe des classes abstraites

Une classe abstraite est définie à l’aide du mot-clé abstract précédant le mot-clé class. Une classe abstraite ne peut pas être instanciée directement, ce qui signifie que tu ne peux pas créer d’objet à partir d’une classe abstraite. Cependant, elle peut servir de classe de base pour d’autres classes dérivées. Voici la syntaxe pour définir une classe abstraite :

public abstract class ClasseAbstraite {
    // Méthodes et propriétés (abstraites ou concrètes) de la classe abstraite
}

6.2 Méthodes abstraites vs méthodes concrètes

Une classe abstraite peut contenir à la fois des méthodes abstraites et des méthodes concrètes.

Méthodes abstraites :
Une méthode abstraite est une méthode déclarée dans une classe abstraite sans implémentation (corps). Elle est marquée avec le mot-clé abstract. Les classes dérivées doivent obligatoirement implémenter toutes les méthodes abstraites héritées de la classe abstraite. Les méthodes abstraites n’ont pas de corps, car elles sont destinées à être redéfinies dans les classes dérivées.  

public abstract class Forme { public abstract double CalculerAire(); }

Méthodes concrètes :
Une méthode concrète est une méthode définie avec une implémentation complète dans la classe abstraite. Les classes dérivées peuvent utiliser directement ces méthodes sans avoir besoin de les redéfinir.

public abstract class Forme {
    public abstract double CalculerAire();
    public void AfficherNom() {
        Console.WriteLine(« Je suis une forme. »);
    }
}

6.3 Utilisation des classes abstraites dans l’héritage

Les classes abstraites sont particulièrement utiles dans le contexte de l’héritage. Elles permettent de définir une structure de base commune pour les classes dérivées tout en laissant certaines méthodes abstraites à implémenter par les classes dérivées. Voici un exemple d’utilisation de classes abstraites dans le contexte de l’héritage :

public abstract class Animal {
    public abstract void EmettreSon();
}
public class Chien : Animal {
    public override void EmettreSon() {
        Console.WriteLine(« Le chien aboie. »);
    }
}
public class Chat : Animal {
    public override void EmettreSon() {
        Console.WriteLine(« Le chat miaule. »);
    }
}

Dans cet exemple, la classe Animal est déclarée comme abstraite avec une méthode abstraite EmettreSon. Les classes dérivées Chien et Chat doivent implémenter la méthode EmettreSon en redéfinissant le comportement hérité de la classe de base Animal. Ainsi, chaque classe dérivée détermine comment l’animal émet son son spécifique.

7. Le Mot-clé sealed

Dans cette section, nous allons explorer le mot-clé sealed en C#. Le mot-clé sealed est utilisé pour restreindre l’héritage d’une classe ou d’une méthode. Lorsqu’une classe est marquée comme sealed, cela signifie qu’elle ne peut pas être utilisée comme classe de base pour d’autres classes dérivées. De même, lorsque tu marques une méthode comme sealed, cela empêche les classes dérivées de redéfinir cette méthode.

 

7.1 Utilisation du mot-clé sealed avec les classes

Pour empêcher une classe d’être utilisée comme classe de base pour d’autres classes dérivées, tu peux marquer la classe avec le mot-clé sealed :

public sealed class MaClasseScellee { // Contenu de la classe scellée }

Dans cet exemple, la classe MaClasseScellee est marquée comme sealed, ce qui signifie qu’aucune autre classe ne pourra hériter de cette classe. Il est important de noter que tu ne peux pas marquer une classe abstraite comme sealed, car une classe abstraite est destinée à être utilisée comme classe de base pour d’autres classes dérivées.

 

7.2 Utilisation du mot-clé sealed avec les méthodes

Le mot-clé sealed peut également être utilisé pour empêcher les classes dérivées de redéfinir une méthode héritée. Pour ce faire, tu dois marquer la méthode avec le mot-clé sealed dans la classe de base :

public class ClasseDeBase {
    public virtual void MethodeVirtuelle() {
        // Implémentation de la méthode virtuelle dans la classe de base
    }
}
public class ClasseDerivee : ClasseDeBase {
    public sealed override void MethodeVirtuelle() {
        // Implémentation spécifique à la classe dérivée
    }
}

Dans cet exemple, la méthode MethodeVirtuelle est marquée comme sealed dans la classe dérivée ClasseDerivee. Cela empêchera toute autre classe dérivée de ClasseDerivee de redéfinir cette méthode.

 

7.3 Limitations du mot-clé sealed

Il est important de noter que l’utilisation du mot-clé sealed doit être réfléchie, car cela limite la flexibilité du code en empêchant l’héritage. Le mot-clé sealed est généralement utilisé pour des classes ou des méthodes qui ont été soigneusement conçues et qui ne doivent pas être étendues ou modifiées par des classes dérivées.

 

7.4 Cas d’utilisation du mot-clé sealed

Voici quelques cas d’utilisation courants pour le mot-clé sealed :

  • Lorsque tu souhaites définir une classe qui ne doit pas être utilisée comme classe de base pour d’autres classes dérivées.
  • Lorsque tu souhaites empêcher les classes dérivées de redéfinir certaines méthodes critiques pour le bon fonctionnement de la classe de base.
  • Lorsque tu veux éviter que certaines parties de votre code soient modifiées ou étendues par d’autres développeurs.

L’héritage en C# est un concept puissant de la programmation orientée objet qui permet de créer des hiérarchies de classes et de partager des fonctionnalités communes entre elles. Grâce à l’héritage, nous pouvons organiser notre code de manière hiérarchique et éviter la duplication de code en réutilisant des fonctionnalités déjà implémentées dans une classe de base.

QCM

Testez vos connaissances sur ce sujet

TL;DR

Nous avons vu que l’héritage en C# s’effectue à l’aide du mot-clé class. Les classes dérivées héritent des propriétés et des méthodes de la classe de base, ce qui leur permet d’accéder aux fonctionnalités déjà implémentées et de les étendre ou de les modifier si nécessaire.

Les modificateurs d’accès en C# jouent un rôle crucial dans l’héritage en déterminant la visibilité des membres hérités dans la classe dérivée. En choisissant les bons modificateurs d’accès, nous pouvons maintenir l’encapsulation du code et contrôler l’accès aux fonctionnalités internes.

Les méthodes et propriétés virtuelles ainsi que les interfaces sont des éléments essentiels pour atteindre le polymorphisme en C#. Les méthodes virtuelles permettent de redéfinir le comportement hérité dans les classes dérivées, tandis que les interfaces définissent des contrats de comportement pour les classes qui les implémentent, offrant ainsi une plus grande flexibilité et extensibilité du code.

Les classes abstraites servent de modèle pour d’autres classes en définissant des méthodes abstraites et concrètes. Elles permettent de créer des hiérarchies de classes bien organisées tout en laissant certaines méthodes abstraites à implémenter par les classes dérivées.

Enfin, nous avons abordé le mot-clé sealed qui permet de restreindre l’héritage d’une classe ou d’une méthode. Il limite la flexibilité du code, mais il peut être utile pour protéger des parties critiques du code ou empêcher des classes dérivées de modifier certaines méthodes.