RSS

Différence en C# entre new et override lors du remplacement d’une fonction à hériter

24 Juin

Au départ, un débutant en C# se demande quelle peut bien être la différence entre new et override, et lequel choisir lorsqu’il souhaite remplacer une méthode dans une classe héritante.

Vocabulaire utilisé ici

  • Classe à hériter = classe de base.
  • Classe héritante = classe dérivée (elle hérite de la classe de base).

En une phrase

Avec override (et donc virtual), le code de la classe à hériter (la classe de base) appellera soit sa propre méthode virtual, soit la méthode d’une classe héritante (dérivée de cette classe de base) si celle-ci la remplace avec override.

Exemple montrant la différence

Le code:

public class ClasseÀHériter
{
    public string FonctionNormale()
    {
        return "FonctionNormale dans la classe de base";
    }
    public virtual string FonctionExplicitementRemplaçable()
    {
        return "FonctionExplicitementRemplaçable dans la classe de base ";
    }
    public string Résultat()
    {
        return
            this.FonctionNormale()  // Appelle toujours la fonction définie dans cette classe.
            + "\n"
            + this.FonctionExplicitementRemplaçable(); // La fonction appelée a pu être remplacée dans une classe héritante.
    }
}

public class ClasseHéritante : ClasseÀHériter
{
    public new string FonctionNormale()
    {
        return "FonctionNormale dans la classe dérivée";
    }
    public override string FonctionExplicitementRemplaçable()
    {
        return "FonctionExplicitementRemplaçable dans la classe dérivée";
    }
    public string AutreRésultat()
    {
        return
            this.FonctionNormale()
            + "\n"
            + this.FonctionExplicitementRemplaçable();
    }
}

public MainWindow()
{
    InitializeComponent();

    // ------------------

    ClasseÀHériter càh = new ClasseÀHériter();
    string scàh = càh.Résultat();

    ClasseHéritante ch = new ClasseHéritante();

    string schr = ch.Résultat();
    string scàhar = ch.AutreRésultat();
}

Résultats:

Classe utilisée Méthode appelée Texte renvoyé par FonctionNormale() Texte renvoyé par FonctionExplicitementRemplaçable()
ClasseÀHériter Résultat() de ClasseÀHériter FonctionNormale dans la classe de base FonctionExplicitementRemplaçable dans la classe de base
ClasseHéritante Résultat() de ClasseÀHériter FonctionNormale dans la classe de base FonctionExplicitementRemplaçable dans la classe dérivée
ClasseHéritante AutreRésultat() de ClasseHéritante FonctionNormale dans la classe dérivée FonctionExplicitementRemplaçable dans la classe dérivée

Interprétation:

  • Lorsqu’on instancie ClasseÀHériter, elle utilise ses propres fonctions.
  • Lorsqu’on hérite de ClasseÀHériter dans ClasseHéritante, la méthode FonctionNormale() appartenant à ClasseÀHériter utilise la méthode FonctionExplicitementRemplaçable() remplacée par ClasseHéritante avec override.
  • Lorsqu’on hérite de ClasseÀHériter (dans ClasseHéritante), la méthode AutreRésultat() de ClasseHéritante utilise les deux méthodes remplacées (avec new et avec override).

Aspect technique

Alors que new se contente de définir une méthode dont le nom remplace celui de la classe à hériter, override redirige les appels à la méthode remplacée même pour le code de la classe de base à hériter.
Cela implique de placer dans la partie statique de la classe à hériter, et dans toutes ses dérivées, un pointeur (un délégué, si vous voulez) qui permet au code de la classe à hériter de trouver un éventuel remplacement (par override) dans les classes héritantes.

Simulation de la virtualisation

Pour comprendre le mécanisme, voici une sorte d’écriture manuelle d’une méthode virtualisée dans une classe:

public class SimulationDeVirtualisation
{
    public delegate string ModèleDeLaMéthodeÀVirtualiser();

    public ModèleDeLaMéthodeÀVirtualiser RedirecteurVersLaMéthodeÀUtiliser; // En fait devrait être statique.

    public SimulationDeVirtualisation() // Constructeur:
    {
        RedirecteurVersLaMéthodeÀUtiliser = MéthodeDebaseRemplaçable;
    }

    private string MéthodeDebaseRemplaçable()
    {
        return "Méthode dans la classe de base";
    }

    public string DitSiMéthodeRemplacée()
    {
        return RedirecteurVersLaMéthodeÀUtiliser();
    }
}

public class ClasseHéritanteAvecMéthodeVirtualisée : SimulationDeVirtualisation
{
    public ClasseHéritanteAvecMéthodeVirtualisée() // Constructeur:
    {
        RedirecteurVersLaMéthodeÀUtiliser = MéthodeDebaseRemplaçante;
    }

    static private string MéthodeDebaseRemplaçante()
    {
        return "Méthode dans la classe dérivée";
    }
}
   
public MainWindow()
{
    InitializeComponent();

    // ------------------

    SimulationDeVirtualisation sv = new SimulationDeVirtualisation();
    string tsv = sv.DitSiMéthodeRemplacée();    // Renvoie "Méthode dans la classe de base".

    ClasseHéritanteAvecMéthodeVirtualisée chamv = new ClasseHéritanteAvecMéthodeVirtualisée();
    string tchamv = chamv.DitSiMéthodeRemplacée(); // Renvoie "Méthode dans la classe dérivée".
}

Je n’ai pas pu implémenter aussi parfaitement que je l’aurai voulu, à cause de limitations de C#, mais on voit bien que la classe de base utilise soit sa propre fonction virtuelle, soit la fonction remplaçante implémentée dans la classe dérivée.

À noter que RedirecteurVersLaMéthodeÀUtiliser devrait être statique, aussi bien dans la classe de base que dans chaque dérivation de cette classe, et que dans ce cas il est inutile de l’initialiser dans le constructeur de chaque classe.

Bien entendu, le compilateur C# s’occupe de ces détails à notre place lorsqu’on utilise les mots-clés virtual et override.
Mais il est bon de savoir ce qu’implique cette utilisation, en terme de ressources et de performances.

Conclusion(s)

  • Virtual vous est utile lorsque vous écrivez une classe à hériter (une classe de base), et que vous voulez y utiliser des informations qui n’existeront que dans les classes qui en hériteront.
  • L’utilisation de Virtual entraîne l’ajout d’un pointeur caché (au sens du C) dans la partie statique des classes à hériter et des classes héritantes, et une redirection dans le code de la classe à hériter. Soit un peu plus de complication.
    Mieux vaut donc éviter de créer des classes virtuelles et leur préférer des remplacements par new lorsque c’est possible. Surtout pour des petites classes souvent instanciées.
  •  
Poster un commentaire

Publié par le 2012-06-24 dans Programmation

 

Étiquettes : , , , , , , , , , ,

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s