1. Introduction
Quand un débutant entend le mot héritage, il se dit que c’est easy : tu prends une classe existante, tu l’agrandis ou tu la modifies un peu — et voilà ! Mais en vrai, y’a plein de subtilités. Les erreurs d’héritage peuvent se manifester de plein de façons : signatures pas bonnes, mots-clés oubliés, design foireux de la hiérarchie — tout ça, ça finit en bugs bien relous.
Parfois, les erreurs se voient direct : le code compile pas. Mais d’autres fois, les bugs te sautent à la figure seulement à l’exécution, genre quand ton nouveau SuperMegaLogger écrit pas du tout ce que les users attendent, ou pire, il écrit rien du tout. Tristesse, déception, des heures de debug — tu connais ?
Viens, on va checker ensemble les erreurs les plus fréquentes liées à l’héritage et à l’override, et on va « soigner » notre code au passage.
2. Le virtual oublié : pourquoi je peux pas override la méthode ?
Le problème
En C#, tu peux override (override) seulement les méthodes qui sont déclarées dans la classe de base avec le mot-clé virtual, ou alors abstract, ou encore override (dans une chaîne d’héritage). Si la méthode n’a pas ce mot, essayer de mettre override dans la classe dérivée va te donner une erreur de compilation.
class Animal
{
public void Speak()
{
Console.WriteLine("L’animal dit quelque chose.");
}
}
class Cat : Animal
{
// Erreur de compilation ! La méthode de base n’est ni virtual, ni abstract, ni override.
public override void Speak()
{
Console.WriteLine("Miaou !");
}
}
L’erreur sera un truc du genre : "'Cat.Speak()': cannot override inherited member 'Animal.Speak()' because it is not marked virtual, abstract, or override".
Comment éviter ce piège ?
Pour pouvoir override des méthodes, pense à les déclarer virtual dans la classe de base. D’ailleurs, si tu designes des classes pour qu’on puisse les étendre, réfléchis bien à quelles méthodes ça vaudrait le coup de rendre overridables.
class Animal
{
public virtual void Speak()
{
Console.WriteLine("L’animal fait un bruit.");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Miaou !");
}
}
Pourquoi faire ça ?
En mettant virtual sur la méthode, tu autorises explicitement les enfants à changer son comportement, et le compilateur devient ton pote — il t’empêchera d’override la mauvaise méthode par accident.
3. Erreur avec les mots-clés override et new
Parfois, c’est l’inverse : l’auteur de la classe dérivée veut « override » une méthode, mais dans la classe de base elle n’est pas virtual. Là, le compilateur te laisse pas utiliser override, mais il accepte new. Mais attention, c’est pas du tout le même délire !
class Dog : Animal
{
// Ce n’est pas un override, mais un masquage (hiding) de la méthode de base.
public new void Speak()
{
Console.WriteLine("Ouaf !");
}
}
Si tu appelles cette méthode via une référence de type Dog — tout roule :
Dog dog = new Dog();
dog.Speak(); // "Ouaf !"
Mais si tu utilises une référence du type de base, c’est la méthode de base qui sera appelée :
Animal dog2 = new Dog();
dog2.Speak(); // "L’animal fait un bruit."
Explication :
Le mot-clé new NE fait PAS d’override, il masque juste la méthode parent. On appelle ça du « hiding ». Ça peut embrouiller, parce que le comportement polymorphe ne marche pas comme tu t’y attends.
Comment éviter les pièges new vs override ?
Si tu veux un vrai override avec du polymorphisme — utilise virtual/override. Si tu ajoutes une fonctionnalité vraiment nouvelle (ou que tu veux cacher le comportement de la méthode de base, mais fais gaffe !), alors tu peux utiliser new.
| Situation | Mot-clé | Polymorphisme ok ? | Comportement via le type de base |
|---|---|---|---|
| Changer le comportement | override | Oui | Méthode de la classe dérivée appelée |
| Cacher/remplacer la méthode | new | Non | Méthode de la classe de base appelée |
4. Signatures de méthodes qui ne matchent pas
Les débutants, et même parfois les devs expérimentés, se plantent en changeant les types de paramètres ou le type de retour dans la méthode héritée. Par exemple, si la méthode de base est public virtual void Print(string message), et que dans la classe dérivée tu fais public override void Print(object message), c’est PAS un override, mais une nouvelle méthode.
class Printer
{
public virtual void Print(string msg)
{
Console.WriteLine("Imprimante de base : " + msg);
}
}
class SmartPrinter : Printer
{
// Erreur de compilation ! La signature ne correspond pas à la méthode de base.
public override void Print(object msg)
{
Console.WriteLine("Imprimante smart : " + msg);
}
}
Astuce :
Il faut que le nom de la méthode, le type de retour et les paramètres (types, nombre, ordre) soient identiques.
Si tu changes par erreur le type d’un paramètre ou que tu te plantes dans le nom — le compilateur va râler.
5. Modificateurs d’accès pas cohérents
Un autre piège classique pour les débutants : les modificateurs d’accès. Une méthode dérivée ne peut pas être plus restrictive que celle de base. Exemple de code pas bon :
public class Vehicle
{
public virtual void StartEngine() { /* ... */ }
}
public class Car : Vehicle
{
// Erreur ! Le modificateur 'private' est plus strict que 'public' dans la base.
private override void StartEngine() { /* ... */ }
}
Que faire ?
Le modificateur d’accès dans la méthode héritée doit être au moins aussi ouvert que celui de la base. La plupart du temps, c’est public ou protected.
6. Méthode abstraite oubliée/en trop
Si dans la classe de base une méthode est abstract, alors la classe dérivée DOIT l’override, sinon la classe devient aussi abstraite (et tu peux pas l’instancier).
abstract class Shape
{
public abstract double Area();
}
class Circle : Shape
{
// Erreur ! La méthode abstraite Area() n’est pas implémentée
}
Solution :
Faut implémenter la méthode :
class Circle : Shape
{
public override double Area()
{
return 3.14 * 2 * 2; // À peu près...
}
}
7. Appeler l’implémentation de base : base.Method()
Parfois, tu veux pas remplacer complètement la méthode, mais juste ajouter un truc. Dans ce cas, on oublie souvent (ou on sait pas) qu’on peut appeler la méthode de base avec le mot-clé base.
class Logger
{
public virtual void Log(string msg)
{
Console.WriteLine("Log de base : " + msg);
}
}
class FancyLogger : Logger
{
public override void Log(string msg)
{
// Tu peux ajouter des trucs et appeler la méthode de base :
Console.WriteLine("[FANCY] " + msg);
base.Log(msg);
}
}
Explication :
Si tu n’appelles pas base.Log(msg), la logique de la classe de base sera complètement zappée.
8. Appeler le constructeur de base (base)
Si la classe de base a besoin de paramètres obligatoires dans son constructeur, la classe dérivée doit explicitement appeler ce constructeur avec le mot-clé base.
class Engine
{
public Engine(int cylinders)
{
Console.WriteLine("Moteur avec cylindres : " + cylinders);
}
}
class RaceEngine : Engine
{
// Erreur de compilation ! Pas de constructeur par défaut dans Engine.
public RaceEngine() { }
}
// Version corrigée :
class RaceEngine2 : Engine
{
public RaceEngine2() : base(8) // On appelle explicitement le constructeur de base
{
Console.WriteLine("Moteur de course prêt !");
}
}
9. Oubli de sealed sur les méthodes
Parfois, tu veux empêcher qu’on override encore ta méthode dans les classes enfants — pour ça, en C# tu as le mot-clé sealed avec override. Si tu le mets pas, quelqu’un pourra override ta méthode, et peut-être casser ta logique.
class Hero
{
public virtual void Attack() => Console.WriteLine("Le héros attaque !");
}
class Warrior : Hero
{
public sealed override void Attack() => Console.WriteLine("Le guerrier frappe !");
}
class Mutant : Warrior
{
// Erreur ! La méthode Attack est sealed plus haut.
// public override void Attack() { ... }
}
Explication :
Comme ça, tu « scelles » l’implémentation à ce niveau, et les classes en dessous pourront plus la changer.
10. Surcharges inutiles ou fausses au lieu d’overrides
Des fois, les devs confondent surcharge (overloading) et override (overriding). Surcharger, c’est faire une méthode du même nom mais avec une signature différente (genre nombre de paramètres), et ça n’a rien à voir avec le polymorphisme.
class Animal
{
public virtual void Eat()
{
Console.WriteLine("L’animal mange.");
}
}
class Panda : Animal
{
// Ce n’est PAS un override ! Juste une nouvelle surcharge.
public void Eat(string what)
{
Console.WriteLine("Le panda mange : " + what);
}
}
...
Animal a = new Panda();
a.Eat(); // Si la méthode est overridée — c’est celle de la classe dérivée qui sera appelée. Sinon — c’est la base
// a.Eat("bambou"); // Erreur de compilation : cette méthode n’existe pas sur Animal
Pour avoir du comportement polymorphe, il faut override, pas juste surcharger.
11. Erreurs de design formelles et informelles
Quand les classes sont mal designées, les héritages deviennent vite incompréhensibles :
- une « carrousel » interne d’overrides sans utiliser base. comme il faut,
- des modificateurs utilisés n’importe comment,
- des hiérarchies trop profondes ou pas claires,
- des méthodes dont la logique est « éparpillée » entre plusieurs niveaux d’héritage,
- pas de commentaires sur les méthodes virtual/abstract,
- des effets de bord pas évidents (genre appeler une méthode virtuelle dans le constructeur de la classe de base alors que la dérivée est pas encore initialisée).
Conseil :
Si tu sens que tu t’emmêles — prends une feuille (méthode old school qui marche !) et dessine la hiérarchie, note où sont les méthodes, qui override quoi, qui appelle quoi, et où il faut mettre base..
Dans les vrais projets, c’est super important aussi de documenter ce qu’on attend de l’override de telle ou telle méthode.
GO TO FULL VERSION