1. Introduction
Imaginons qu’on a une appli pour gérer les animaux dans un zoo (ouais, le même exemple qu’on a commencé à développer dans les leçons précédentes). On a créé une classe de base Animal avec une méthode MakeSound, pour que tous les animaux puissent "faire" un bruit.
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("L’animal fait un bruit.");
}
}
Mais là, on se rend compte que le lion et le perroquet font des bruits complètement différents. Le générique "L’animal fait un bruit" ne marche plus. C’est là que la redéfinition de méthodes entre en jeu. Tu veux que chaque descendant ait son propre son !
2. Syntaxe : comment marche override
Règles de base
- Pour redéfinir une méthode, elle doit être marquée dans la classe de base comme virtual (ou abstract).
- Dans la classe dérivée, on utilise le mot-clé override devant la méthode avec la même signature.
Exemple : lions et perroquets
public class Lion : Animal
{
public override void MakeSound()
{
Console.WriteLine("Rrrrrr !");
}
}
public class Parrot : Animal
{
public override void MakeSound()
{
Console.WriteLine("Popka est un idiot !");
}
}
Maintenant, si tu crées une liste d’animaux et que tu appelles MakeSound pour chacun, chacun "parlera" à sa façon :
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Animal()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
// Sortie :
// Rrrrrr !
// Popka est un idiot !
// L’animal fait un bruit.
3. Table de dispatch virtuel (v-table)
Quand tu utilises virtual et override, le compilateur génère pour la classe une "table virtuelle de méthodes" spéciale (v-table).
Quand tu appelles une méthode via une référence de la classe de base, le CLR regarde dans la table : est-ce que cette méthode a été redéfinie dans un descendant ? Si oui — il appelle la version du descendant.
C’est ça la "magie" du late binding, ou du polymorphisme dynamique.
4. On assemble tout : on continue à développer notre appli
Ajoutons encore une classe :
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Ouaf-ouaf !");
}
}
Et on utilise ça dans notre mini-appli de gestion des animaux :
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Dog()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
Maintenant, ton code est super extensible et facile à maintenir, et ajouter de nouveaux animaux, c’est un vrai plaisir !
5. Redéfinir des propriétés et override avec valeur de retour
Les méthodes, c’est pas tout ce qu’on peut redéfinir. Tu peux aussi redéfinir des propriétés virtuelles et des indexeurs.
public class Animal
{
public virtual string Name { get; set; } = "Animal";
}
public class Lion : Animal
{
public override string Name { get; set; } = "Lion";
}
C’est super pratique quand tu veux préciser des infos pour une espèce en particulier.
6. Implémentation de base avec base
Parfois, tu veux juste étendre le comportement de la méthode de base, pas la remplacer complètement.
Pour ça, tu appelles l’implémentation de la méthode de base avec le mot-clé base à l’intérieur de la méthode redéfinie.
public class Parrot : Animal
{
public override void MakeSound()
{
base.MakeSound(); // "L’animal fait un bruit."
Console.WriteLine("Popka est un idiot !");
}
}
Dans ce cas, le perroquet va d’abord faire le bruit "générique" du zoo, puis sortir son fameux "Popka est un idiot !".
7. À quoi ça sert en vrai ?
Comprendre le mécanisme de override est indispensable dans quasiment tous les projets C# sérieux où on fait de la POO.
- Des modèles d’animaux dans notre zoo ? Déjà fait.
- Créer des contrôles custom pour l’UI : Tu redéfinis les méthodes visuelles standard pour ajouter ta logique.
- Logique métier flexible : Tu construis le "squelette" du comportement dans les classes de base, et tu mets les détails dans les héritiers.
- Tests et mocks : Tu peux facilement "remplacer" des méthodes via des héritiers pour les tests unitaires.
- Plugins et extensions : Interface ou classe de base abstraite, plein d’implémentations — et tout marche grâce à la bonne redéfinition.
8. sealed override et méthodes virtuelles en chaîne
Si jamais tu veux empêcher quelqu’un plus loin dans la hiérarchie de redéfinir ta méthode déjà redéfinie, tu peux utiliser sealed override :
public class Base
{
public virtual void Foo() { }
}
public class Middle : Base
{
public sealed override void Foo() { }
}
public class Last : Middle
{
public override void Foo() {} // Erreur — impossible de redéfinir !
}
Ça permet de "fermer" la chaîne de redéfinitions là où c’est critique pour que le système marche bien.
9. Nouvelle méthode (mot-clé new)
Parfois, tu veux déclarer dans l’héritier une méthode avec la même signature, mais la méthode de base n’est pas virtual.
Dans ce cas, tu peux utiliser le mot-clé new — mais ce n’est pas du polymorphisme, c’est plutôt du "camouflage" :
public class Animal
{
public void MakeSound()
{
Console.WriteLine("Je suis un animal !");
}
}
public class Cat : Animal
{
public new void MakeSound()
{
Console.WriteLine("Je suis un chat !");
}
}
Animal animal = new Cat();
animal.MakeSound(); // "Je suis un animal !"
Cat cat = new Cat();
cat.MakeSound(); // "Je suis un chat !"
Ici, tout dépend du type de la variable au moment de l’appel ! Donc pour du vrai polymorphisme dynamique, utilise toujours la combo virtual + override.
10. Erreurs typiques lors de la redéfinition de méthodes
Erreur n°1 : essayer de redéfinir une méthode qui n’a pas été déclarée comme virtual, abstract ou override.
En C#, tu ne peux pas redéfinir des méthodes normales. Si la méthode dans la classe de base n’a pas de modificateur spécial (virtual, abstract ou override), essayer de mettre override dans la classe dérivée va donner une erreur de compilation.
Erreur n°2 : signature de méthode différente.
Pour qu’une méthode soit vraiment redéfinie, son nom, son type de retour et ses paramètres doivent être exactement les mêmes que dans la classe de base. La moindre différence (genre un autre type de paramètre) va créer une nouvelle méthode, pas une redéfinition.
Erreur n°3 : oublier le modificateur override.
Si tu définis une méthode avec la même signature que dans la classe de base, mais sans mettre override, le compilateur ne considère pas ça comme une redéfinition. Ça s’appelle masquage de méthode (on verra ça à part). Dans ce cas, appeler la méthode via une variable du type de base va donner un résultat inattendu — c’est la méthode de base qui sera appelée, pas la tienne.
GO TO FULL VERSION