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 se produit, 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éthodesvirtuelles, 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 votre code source, vous pouvez appeler une méthode sur une classe de base et provoquer l’exécution de la version d’une 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 spécifiques de formes que l’utilisateur créera. 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 List<Shape> objet et ajoutez Circle , Triangle et Rectangle à celui-ci.

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 sur chaque objet Shape de la liste. Bien que chaque objet de la liste ait un type Shape déclaré, 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 sera appelée.

// 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 obtient toutes les méthodes, les champs, les propriétés et les événements de la classe de base. Le concepteur de la classe dérivée a des choix différents pour le comportement des méthodes virtuelles :

  • La classe dérivée peut substituer des membres virtuels dans la classe de base, 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 substituer, en préservant le comportement existant, mais en permettant à d’autres classes dérivées de substituer la méthode.
  • La classe dérivée peut définir une nouvelle implémentation non virtuelle de ces membres qui masquent 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 ; seules les méthodes, les propriétés, les événements et les 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 les membres de la classe de base avec les nouveaux membres

Si vous souhaitez que votre classe dérivée ait un membre portant le même nom qu’un membre dans une classe de base, vous pouvez utiliser le mot clé New pour masquer le membre de la 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 masqués de la classe de base 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 substituer des 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 B que la classe dérive de A , et que la classe C dérive de B , la classe C hérite du membre virtuel et peut le substituer, que la classe B ait déclaré ou non 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 de placer le sealed mot clé avant le override mot clé 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 à une classe dérivée de C . Il est toujours virtuel pour les instances de C , même s’ils sont convertis en type B ou en type A . Les méthodes sealed peuvent être remplacées par des classes dérivées à l’aide du new mot clé, 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 suit les règles d’héritage virtuel, en routant ces appels à l’implémentation de DoWork sur 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.