Partager via


Polymorphisme

Le polymorphisme est souvent considéré comme le troisième pilier d'une programmation orientée objet, après l'encapsulation et l'héritage. Le polymorphisme est le mot grec qui signifie « plusieurs formes » et il prend deux aspects distincts :

  • Au moment de l’exécution, les objets d’une classe dérivée peuvent être traités comme des objets d’une classe de base dans les paramètres de méthode et les collections ou les tableaux. Lorsque ce polymorphisme intervient, le type déclaré de l'objet n'est plus identique à son type au moment de l'exécution.
  • Les classes de base peuvent définir et implémenter des méthodes virtuelles, et les classes dérivées peuvent les substituer, ce qui signifie qu’elles fournissent leur propre définition et implémentation. Au moment de l'exécution, quand le code client appelle la méthode, le CLR recherche le type au moment de l'exécution et appelle cette substitution de la méthode virtuelle. Dans le code source, vous pouvez appeler une méthode dans une classe de base, et provoquer l'exécution d'une version de la classe dérivée de la méthode.

Les méthodes virtuelles vous permettent d'utiliser des groupes d'objets liés de façon uniforme. Par exemple, si vous avez une application de dessin qui permet à un utilisateur de créer différents types de formes sur une surface de dessin. Vous ne savez pas au moment de la compilation les types de formes spécifiques que l'utilisateur va créer. Cependant, l'application doit conserver une trace des différents types de formes créés et les mettre à jour en réponse aux actions de la souris. Vous pouvez utiliser le polymorphisme pour résoudre ce problème en deux étapes :

  1. Créer une hiérarchie de classe dans laquelle chaque classe de forme dérive d'une classe de base commune.
  2. Utiliser une méthode virtuelle pour appeler la méthode appropriée dans une classe dérivée via un seul appel à la méthode de classe de base.

Tout d'abord, créez une classe de base appelée Shape, et des classes dérivées telles que Rectangle, Circle, et Triangle. Donnez à la classe Shape une méthode virtuelle appelée Draw, et substituez-la dans chaque classe dérivée pour dessiner une forme spécifique représentée par la classe. Créez un objet List<Shape> et ajoutez-lui un Circle, un Triangle et un Rectangle.

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
public class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
public class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

Pour mettre à jour la surface de dessin, utilisez une boucle foreach pour l’itération dans la liste et appelez la méthode Draw sur chaque objet Shape de la liste. Même si chaque objet de la liste a un type déclaré égal à Shape, il s'agit du type au moment de l'exécution (la version substituée de la méthode dans chaque classe dérivée) qui est appelé.

// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used wherever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
    new Rectangle(),
    new Triangle(),
    new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
    shape.Draw();
}
/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
*/

En C#, chaque type est polymorphique, car tous les types, y compris les types définis par l'utilisateur, héritent de Object.

Vue d'ensemble du polymorphisme

Membres virtuels

Lorsqu’une classe dérivée hérite d’une classe de base, elle inclut tous les membres de la classe de base. Tout le comportement déclaré dans la classe de base fait partie de la classe dérivée. Ainsi, les objets de la classe dérivée sont traités comme les objets de la classe de base. Les modificateurs d’accès (public, protected, private, etc.) déterminent si ces membres sont accessibles à partir de l’implémentation de classe dérivée. Les méthodes virtuelles offrent au concepteur différents choix en termes de comportement de la classe dérivée :

  • La classe dérivée peut remplacer les membres virtuels dans la classe de base, en définissant un nouveau comportement.
  • La classe dérivée peut hériter de la méthode de classe de base la plus proche sans la remplacer, en conservant le comportement existant, mais en permettant à d’autres classes dérivées de remplacer la méthode.
  • La classe dérivée peut définir une nouvelle implémentation non virtuelle de ces membres qui masque les implémentations de la classe de base.

Une classe dérivée ne peut substituer un membre de classe de base que si le membre de classe de base est déclaré comme étant virtual ou abstract. Le membre dérivé doit utiliser le mot clé override pour indiquer explicitement que la méthode est conçue pour participer à l’appel virtuel. Le code suivant est fourni à titre d'exemple :

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

Les champs ne peuvent pas être virtuels ; seuls les méthodes, propriétés, événements et indexeurs peuvent être virtuels. Quand une classe dérivée est substituée à un membre virtuel, ce membre est appelé lors de l'accès à une instance de cette classe en tant qu'instance de la classe de base. Le code suivant est fourni à titre d'exemple :

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = B;
A.DoWork();  // Also calls the new method.

Les méthodes et propriétés virtuelles permettent aux classes dérivées d'étendre une classe de base sans avoir besoin d'utiliser l'implémentation de classe de base d'une méthode. Pour plus d’informations, consultez Versioning avec les mots clés override et new. Une interface fournit un autre moyen pour définir une méthode ou un ensemble de méthodes dont l'implémentation est confiée aux classes dérivées.

Masquer des membres de classe de base par de nouveaux membres

Si vous souhaitez que votre classe dérivée ait un membre portant le même nom qu’un membre de classe de base, vous pouvez utiliser le nouveau mot clé pour masquer le membre de classe de base. Le mot clé new est placé avec le type de retour d'un membre de classe qui est remplacé. Le code suivant est fourni à titre d'exemple :

public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}

Les membres de classe de base masqués sont accessibles à partir du code client en effectuant un cast de l'instance de la classe dérivée vers une instance de la classe de base. Par exemple :

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

Empêcher les classes dérivées de remplacer les membres virtuels

Les membres virtuels restent virtuels, quel que soit le nombre de classes déclarées entre le membre virtuel et la classe qui l'a déclaré à l'origine. Si la classe A déclare un membre virtuel, et la classe B dérive de A, et la classe C dérive de B, la classe C hérite du membre virtuel et peut le remplacer, que la classe B ait ou non déclaré une substitution pour ce membre. Le code suivant est fourni à titre d'exemple :

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

Une classe dérivée peut arrêter l’héritage virtuel en déclarant une substitution comme étant sealed. L’arrêt de l’héritage nécessite l'ajout du mot clé sealed avant le mot clé override dans la déclaration de membre de classe. Le code suivant est fourni à titre d'exemple :

public class C : B
{
    public sealed override void DoWork() { }
}

Dans l'exemple précédent, la méthode DoWork n'est plus virtuelle pour les classes dérivées de C. Elle est toujours virtuelle pour les instances de C, même si elles sont castées en type B ou A. Les méthodes sealed peuvent être remplacées par des classes dérivées en utilisant le mot clé new, comme le montre l'exemple suivant :

public class D : C
{
    public new void DoWork() { }
}

Dans ce cas, si DoWork est appelé sur D à l'aide d'une variable de type D, le nouveau DoWork est appelé. Si une variable de type C, B ou A est utilisée pour accéder à une instance de D, un appel à DoWork suivra les règles de l'héritage virtuel, en routant ces appels vers l'implémentation de DoWork dans la classe C.

Accéder aux membres virtuels de la classe de base à partir de classes dérivées

Une classe dérivée qui a remplacé ou écrasé une méthode ou une propriété peut encore accéder à la méthode ou à la propriété dans la classe de base avec le mot clé base. Le code suivant est fourni à titre d'exemple :

public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}

Pour plus d’informations, consultez base.

Notes

Il est recommandé que les membres virtuels utilisent base pour appeler l'implémentation de classe de base de ce membre dans leur propre implémentation. L'exécution du comportement de classe de base permet à la classe dérivée de se concentrer sur l'implémentation du comportement spécifique à la classe dérivée. Si l'implémentation de classe de base n'est pas appelée, la classe dérivée doit rendre son comportement compatible avec le comportement de la classe de base.