Nous avons vu pour l’instant la théorie de l’héritage. Que les objets chiens héritaient des comportements des objets Animaux, que les labradors héritaient des comportements des chiens, etc.
Passons maintenant à la pratique et créons une classe
Animal et une classe
Chien qui en hérite. Nous allons créer des classes relativement courtes et nous nous limiterons dans le nombre d’actions ou de propriétés de celles-ci. Par exemple, nous pourrions imaginer que la classe
Animal possède une propriété
NombreDePattes qui est un entier et une méthode
Respirer qui affiche le détail de l’action. Ce qui donne :
Code : C# | public class Animal
{
public int NombreDePattes { get; set; }
public void Respirer()
{
Console.WriteLine("Je respire");
}
}
|
La classe
Chien dérive de la classe
Animal et peut donc hériter de certains de ses comportements. En l’occurrence, la classe
Chien héritera de tout ce qui est public ou protégé, identifiés comme vous le savez désormais par les mots clés
public et
protected.
Le chien sait également faire quelque chose qui lui est propre, à savoir aboyer. Il possèdera donc une méthode supplémentaire. Ce qui donne :
Code : C# | public class Chien : Animal
{
public void Aboyer()
{
Console.WriteLine("Wouaf !");
}
}
|
On représente la notion d’héritage en ajoutant après la classe le caractère « : » suivi de la classe mère.
Ici, nous avons défini une classe publique
Chien qui hérite de la classe
Animal.
Nous pouvons dès à présent créer des objets
Animal et des objets
Chien, par exemple :
Code : C# | Animal animal = new Animal { NombreDePattes = 4 };
animal.Respirer();
Console.WriteLine();
Chien chien = new Chien { NombreDePattes = 4 };
chien.Respirer();
chien.Aboyer();
|
Si nous exécutons ce code, nous aurons :
Nous nous rendons bien compte que l’objet
Chien, bien que n’ayant pas défini la propriété
NombreDePattes ou la méthode
Respirer() dans le corps de sa classe, est capable d’avoir des pattes et de faire l’action respirer.
Il a hérité ces comportements de l’objet
Animal, en tous cas, ceux qui sont publiques.
Rajoutons deux variables membres de la classe
Animal :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12 | public class Animal
{
private bool estVivant;
public int age;
public int NombreDePattes { get; set; }
public void Respirer()
{
Console.WriteLine("Je respire");
}
}
|
L’entier
age est public alors que le booléen
estVivant est privé. Si nous tentons de les utiliser depuis la classe fille
Chien, comme ci-dessous :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | public class Chien : Animal
{
public void Aboyer()
{
Console.WriteLine("Wouaf !");
}
public void Vieillir()
{
age++;
}
public void Naissance()
{
age = 0;
estVivant = true; // Erreur > 'MaPremiereApplication.Animal.estVivant'
// est inaccessible en raison de son niveau de protection
}
}
|
Nous voyons qu’il est tout à fait possible d’utiliser la variable
age depuis la méthode
Vieillir() alors que l’utilisation du booléen
estVivant provoque une erreur de compilation.
Vous avez bien compris que celui-ci était inaccessible car il est défini comme membre privé. Pour l’utiliser, on pourra le rendre public par exemple.
Il existe par contre un autre mot clé qui permet de rentre des variables/propriétés/méthodes inaccessibles depuis un autre objet tout en le rendant accessible depuis des classes filles. Il s’agit du mot clé
protected.
Si nous l’utilisons à la place de
private pour définir la visibilité du booléen
estVivant, nous pourrons nous rendre compte que la classe
Chien peut désormais compiler :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | public class Animal
{
protected bool estVivant;
[… Extrait de code supprimé …]
}
public class Chien : Animal
{
[… Extrait de code supprimé …]
public void Naissance()
{
age = 0;
estVivant = true; // compilation OK
}
}
|
Par contre, cette variable est toujours inaccessible depuis d’autres classes, comme l’est également une variable privée. Dans notre classe
Program, l’instruction suivante :
Code : C#
provoquera l’erreur de compilation que nous connaissons désormais bien :
Code : Console | 'MaPremiereApplication.Animal.estVivant' est inaccessible en raison de son niveau de protection |
Le mot clé
protected prend tout son intérêt dès que nous avons à faire avec l’héritage. Nous verrons un peu plus bas d’autres exemples de ce mot clé.
Nous avons dit dans l’introduction qu’un objet B qui dérive de l’objet A est « une sorte » d’objet A. Dans notre exemple du dessus, le Chien est une sorte d’Animal.
Cela veut dire que nous pouvons utiliser un chien en tant qu’animal. Par exemple, le code suivant :
Code : C# | Animal animal = new Chien { NombreDePattes = 4 };
|
est tout à fait correct. Nous disons que notre variable
animal, de type
Animal est une instance de
Chien.
Avec cette façon d’écrire, nous avons réellement instancié un objet
Chien mais celui-ci sera traité en tant qu’
Animal. Cela veut dire qu’il sera capable de
Respirer() et d’avoir des pattes. Par contre, même si en vrai, notre objet serait capable d’aboyer, le fait qu’il soit manipulé en tant qu’
Animal nous empêche de pouvoir le faire
Aboyer.
Cela veut dire que le code suivant :
Code : C# | Animal animal = new Chien { NombreDePattes = 4 };
animal.Respirer();
animal.Aboyer(); // erreur de compilation
|
provoquera une erreur de compilation pour indiquer que la classe
Animal ne contient aucune définition pour la méthode
Aboyer(). Ce qui est normal, un animal ne sait pas forcément aboyer...
Quel est l’intérêt alors d’utiliser le chien en tant qu’animal ?
Bonne question.
Pour y répondre, nous allons enrichir notre classe
Animal, garder notre classe
Chien et créer une classe
Chat qui hérite également d’
Animal. Ce pourrait être :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | public class Animal
{
protected string prenom;
public void Respirer()
{
Console.WriteLine("Je suis " + prenom + " et je respire");
}
}
public class Chien : Animal
{
public Chien(string prenomDuChien)
{
prenom = prenomDuChien;
}
public void Aboyer()
{
Console.WriteLine("Wouaf !");
}
}
public class Chat : Animal
{
public Chat(string prenomDuChat)
{
prenom = prenomDuChat;
}
public void Miauler()
{
Console.WriteLine("Miaou");
}
}
|
Nous forçons les chiens et les chats à avoir un nom, hérité de la classe
Animal, grâce au constructeur afin de pouvoir les identifier facilement.
Le chat garde le même principe que le chien, sauf que nous avons une méthode
Miauler() à la place de la méthode
Aboyer() … Ce qui est somme toute logique.
L’idée est de pouvoir utiliser nos chiens et nos chats ensemble comme des animaux, par exemple en utilisant une liste.
Pour illustrer ce fonctionnement, donnons vie à quelques chiens et à quelques chats grâce à nos pouvoirs de développeur et mettons-les dans une liste :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12 | List<Animal> animaux = new List<Animal>();
Animal milou = new Chien("Milou");
Animal dingo = new Chien("Dingo");
Animal idefix = new Chien("Idéfix");
Animal tom = new Chat("Tom");
Animal felix = new Chat("Félix");
animaux.Add(milou);
animaux.Add(dingo);
animaux.Add(idefix);
animaux.Add(tom);
animaux.Add(felix);
|
Nous avons dans un premier temps instancié une liste d’animaux à laquelle nous avons rajouté 3 chiens et 2 chats, chacun étant considéré comme un animal puisqu’ils sont tous des sortes d’animaux, grâce à l’héritage.
Maintenant, nous n’avons plus que des animaux dans la liste.
Il sera donc possible de les faire tous respirer en une simple boucle :
Code : C# | foreach (Animal animal in animaux)
{
animal.Respirer();
}
|
ce qui donnera :
Et voilà, c’est super simple.
Imaginez le bonheur de Noé sur son arche quand il a compris que grâce à la POO, il pouvait faire respirer tous les animaux en une seule boucle ! Quel travail économisé.

Peu importe ce qu’il y a dans la liste, des chiens, des chats, des hamsters, nous savons que ce sont tous des animaux et qu’ils savent tous respirer.
Vous avez sans doute remarqué que nous faisons la même chose dans le constructeur de la classe
Chien et dans celui de la classe
Chat. Deux fois la même chose… Ce n’est pas terrible. Peut-être y a-t-il un moyen de factoriser tout ça ?
Effectivement, il est possible également d’écrire nos classes de cette façon :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 | public class Animal
{
protected string prenom;
public Animal(string prenomAnimal)
{
prenom = prenomAnimal;
}
public void Respirer()
{
Console.WriteLine("Je suis " + prenom + " et je respire");
}
}
public class Chien : Animal
{
public Chien(string prenomDuChien) : base(prenomDuChien)
{
}
public void Aboyer()
{
Console.WriteLine("Wouaf !");
}
}
public class Chat : Animal
{
public Chat(string prenomDuChat) : base(prenomDuChat)
{
}
public void Miauler()
{
Console.WriteLine("Miaou");
}
}
|
Qu’est-ce qui change ?
Eh bien la classe
Animal possède un constructeur qui prend en paramètre un prénom et qui le stocke dans sa variable privée. C’est elle qui fait le travail d’initialisation.
Il devient alors possible pour les constructeurs des classes filles d’appeler le constructeur de la classe mère afin de faire l’affectation du prénom. Pour cela, on utilise les deux points suivis du mot clé base qui signifie « appelle-moi le constructeur de la classe du dessus » auquel nous passons la variable en paramètres.
Avec cette écriture un peu barbare, il devient possible de factoriser des initialisations qui ont un sens pour toutes les classes filles. Dans notre cas, je veux que tous les objets qui dérivent d’
Animal puissent facilement définir un prénom.
Il faut aussi savoir que si nous appelons le constructeur par défaut d’une classe qui n'appelle pas explicitement un constructeur spécialisé d'une classe mère, alors celui-ci appellera automatiquement le constructeur par défaut de la classe dont il hérite. Modifions à nouveau nos classes pour avoir :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | public class Animal
{
protected string prenom;
public Animal()
{
prenom = "Marcel";
}
public void Respirer()
{
Console.WriteLine("Je suis " + prenom + " et je respire");
}
}
public class Chien : Animal
{
public void Aboyer()
{
Console.WriteLine("Wouaf !");
}
}
public class Chat : Animal
{
public Chat(string prenomDuChat)
{
prenom = prenomDuChat;
}
public void Miauler()
{
Console.WriteLine("Miaou");
}
}
|
Ici, la classe
Animal met un prénom par défaut dans son constructeur. Le chien n’a pas de constructeur et le chat en a un qui accepte un paramètre.
Il est donc possible de créer un
Chien sans qu’il ait de prénom mais il est obligatoire d’en définir un pour le chat. Sauf que lorsque nous instancierons notre objet
chien, il appellera automatiquement le constructeur de la classe mère et tous nos chiens s’appelleront Marcel :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | static void Main(string[] args)
{
List<Animal> animaux = new List<Animal>();
Animal chien = new Chien();
Animal tom = new Chat("Tom");
Animal felix = new Chat("Félix");
animaux.Add(chien);
animaux.Add(tom);
animaux.Add(felix);
foreach (Animal animal in animaux)
{
animal.Respirer();
}
}
|
Ce qui donne :
Il est également possible d’appeler un constructeur à partir d’un autre constructeur.
Prenons l’exemple suivant :
Code : C# | public class Voiture
{
private int vitesse;
public Voiture(int vitesseVoiture)
{
vitesse = vitesseVoiture;
}
}
|
Si nous souhaitons rajouter un constructeur par défaut qui initialise la vitesse à 10 par exemple, nous pourrons faire :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | public class Voiture
{
private int vitesse;
public Voiture()
{
vitesse = 10;
}
public Voiture(int vitesseVoiture)
{
vitesse = vitesseVoiture;
}
}
|
Ou encore :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13 | public class Voiture
{
private int vitesse;
public Voiture() : this(10)
{
}
public Voiture(int vitesseVoiture)
{
vitesse = vitesseVoiture;
}
}
|
Ici, l’utilisation du mot clé
this, suivi d’un entier permet d’appeler le constructeur qui possède un paramètre entier au début du constructeur par défaut.
Inversement, nous pouvons appeler le constructeur par défaut d’une classe depuis un constructeur possédant des paramètres afin de pouvoir bénéficier des initialisations de celui-ci :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | public class Voiture
{
private int vitesse;
private string couleur;
public Voiture()
{
vitesse = 10;
}
public Voiture(string couleurVoiture) : this()
{
couleur = couleurVoiture;
}
}
|
Puisque nous sommes à parler d’héritage, il faut savoir que tous les objets que nous créons ou qui sont disponibles dans le framework .NET héritent d’un objet de base. On parle en général d’un « super objet ». L’intérêt de dériver d’un tel objet est de permettre à tous les objets d’avoir certains comportements en commun, mais également de pouvoir éventuellement tous les traiter en tant qu’objet.
Notre super objet est représenté par la classe
Object qui définit plusieurs méthodes. Vous les avez déjà vues si vous avez regardé dans la complétion automatique après avoir créé un objet.
Prenons une classe toute vide, par exemple :
Code : C# | public class ObjetVide
{
}
|
Si nous instancions cet objet et que nous souhaitons l’utiliser, nous verrons que la complétion automatique nous propose des méthodes que nous n’avons jamais créées :
Nous voyons plusieurs méthodes, comme la méthode
Equals ou
GetHashCode ou
GetType ou encore
ToString.
Comme vous l’avez compris, ce sont des méthodes qui sont définies dans la classe
Object. La méthode
ToString par exemple permet d’obtenir une représentation de l’objet sous la forme d’une chaine de caractères.
C’est une méthode qui va souvent nous servir, nous y reviendrons un peu plus tard.
Ce super-objet est du type
Object, mais on utilise généralement son alias
object.
Ainsi, il est possible d’utiliser tous nos objets comme des
object et ainsi utiliser les méthodes qui sont définies sur la classe
Object. Ce qui nous permet de faire :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | static void Main(string[] args)
{
ObjetVide monObjetVide = new ObjetVide();
Chien chien = new Chien();
int age = 30;
string prenom = "Nicolas";
AfficherRepresentation(monObjetVide);
AfficherRepresentation(chien);
AfficherRepresentation(age);
AfficherRepresentation(prenom);
}
private static void AfficherRepresentation(object monObjetVide)
{
Console.WriteLine(monObjetVide.ToString());
}
|
Ce qui donne :
Comme indiqué, la méthode
ToString() permet d’afficher la représentation par défaut d’un objet. Vous aurez remarqué qu’il y a une différence suivant ce que nous passons. En effet, la représentation par défaut des types référence correspond au nom du type, à savoir son espace de nom suivi du nom de sa classe. Pour ce qui est des types valeur, il contient en général la valeur du type, à l’exception des structures que nous n’avons pas encore vues et que nous aborderons un peu plus loin.
L’intérêt dans cet exemple de code est de voir que nous pouvons manipuler tout comme un
object. D’une manière générale, vous aurez peu l’occasion de traiter vos objets en tant qu’
object car il est vraiment plus intéressant de profiter pleinement du type, l’
object étant peu utilisable.
Notez que l’héritage de object est automatique. Nul besoin d’utiliser la syntaxe d’héritage que nous avons déjà vue.
J’en profite maintenant que vous connaissez la méthode
ToString() pour parler d’un point qui a peut-être titillé vos cerveaux.
Dans la première partie, nous avions fait quelque chose du genre :
Code : C# | int vitesse = 20;
string chaine = "La vitesse est " + vitesse + " km/h";
|
La variable
vitesse est un entier. La chaîne
La vitesse est est une chaine de caractères. Nous essayons d’ajouter un entier à une chaîne alors que j’ai dit qu’ils n’étaient pas compatibles entre eux ! Et pourtant cela fonctionne.
Effectivement, c’est bizarre.

Nous concaténons une chaîne à un entier avec l’opérateur + et nous concaténons encore une chaîne.
Et si je fais l’inverse :
Code : C#
cela provoque une erreur de compilation. C’est logique, on ne peut pas ajouter un entier et une chaîne de caractères. Alors pourquoi cela fonctionne dans l’autre sens ?
Ce qu’il se passe en fait dans l’instruction :
Code : C# | string chaine = "La vitesse est " + vitesse + " km/h";
|
c’est que le compilateur se rend compte que nous concaténons une chaîne avec un autre objet, peu importe que ce soit un entier ou un objet complexe. Alors pour que ça fonctionne, il demande une représentation de l’objet sous la forme d’une chaîne de caractères. Nous avons vu que ceci se faisait en appelant la méthode
ToString() qui est héritée de l’objet racine
Object.
L’instruction est donc équivalente à :
Code : C# | string chaine = "La vitesse est " + vitesse.ToString() + " km/h";
|
Dans le cas d’un type valeur comme un entier, la méthode
ToString() renvoie la représentation interne de la valeur, à savoir "20". Dans le cas d’un objet complexe, elle aurait renvoyé le nom du type de l’objet.
Avant de terminer, il est important d’indiquer que le C# n’autorise pas l’héritage multiple.
Ainsi, si nous possédons une classe
Carnivore et une classe
EtreVivant, il ne sera pas possible de faire hériter directement un objet
Homme de l’objet
Carnivore et de l’objet
EtreVivant.
Ainsi, le code suivant :
Code : C# | public class Carnivore
{
}
public class EtreVivant
{
}
public class Homme : Carnivore, EtreVivant
{
}
|
provoquera l’erreur de compilation suivante :
Code : Console | La classe 'MaPremiereApplication.Homme' ne peut pas avoir plusieurs classes de base : 'MaPremiereApplication.Carnivore' et 'EtreVivant' |
Il est impossible de dériver de deux objets en même temps.
Si par contre, cela est pertinent, nous pourrons faire un héritage en cascade afin que
Carnivore dérive de
EtreVivant et que
Homme dérive de
Carnivore :
Code : C# | public class Carnivore : EtreVivant
{
}
public class EtreVivant
{
}
public class Homme : Carnivore
{
}
|
Cependant, il n’est pas toujours pertinent d'opérer de la sorte. Notre
Homme pourrait être à la fois
Carnivore et
Frugivore, cependant cela n’a pas de sens qu’un carnivore soit également frugivore, ou l’inverse.
Oui mais tu as dit que chaque objet dérivait du super objet Object, s’il dérive d’une autre classe comme un chien dérive d’un animal, ça fait bien deux classes dont il dérive…
Effectivement, mais dans ce cas-là, ce n’est pas pareil. Comme il est automatique de dériver de
object, c’est comme si on avait le chien qui hérite de animal qui hérite lui-même de
object. Le C# est assez malin pour ça.