Polymorphisme (Guide de programmation C#)

Le polymorphisme est souvent connu sous le nom de troisième pilier de la programmation orientée objet, après l'encapsulation et l'héritage. Polymorphisme est un mot grec qui signifie « de forme multiple » ; ce terme a deux aspects distincts :

  1. Au moment de l'exécution, les objets d'une classe dérivée peuvent être traités comme objets d'une classe de base à des emplacements tels que des paramètres de méthode et des collections ou tableaux. Lorsque cela se produit, le type déclaré de l'objet n'est plus identique à son type à l'exécution.

  2. 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, lorsque le code client appelle la méthode, le CLR recherche le type à l'exécution de l'objet et appelle cette substitution de la méthode virtuelle. Par conséquent, dans votre code source vous pouvez appeler une méthode 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 connexes de manière uniforme. Par exemple, supposez que vous possédez une application de dessin qui permet à un utilisateur de créer différents types de formes sur une surface de dessin. Vous ne connaissez pas, au moment de la compilation, les types spécifiques de formes que l'utilisateur créera. L'application doit toutefois effectuer le suivi de tous les différents types de formes créées et doit les mettre à jour en réponse aux actions de la souris de l'utilisateur. Vous pouvez utiliser le polymorphisme pour résoudre ce problème en deux étapes de base :

  1. Créez une hiérarchie de classes dans laquelle chaque classe de forme spécifique dérive d'une classe de base courante.

  2. Utilisez une méthode virtuelle pour appeler la méthode appropriée sur toute classe dérivée par le biais d'un appel unique à la méthode de classe de base.

En premier lieu, créez une classe de base nommée Shape et des classes dérivées telles que Rectangle, Circle et Triangle. Donnez à la classe Shape une méthode virtuelle nommée Draw et substituez-la dans chaque classe dérivée pour dessiner la forme particulière représentée par la classe. Créez un objet List<Shape> et ajoutez-y un élément Circle, un élément Triangle et un élément Rectangle. Pour mettre à jour la surface de dessin, utilisez une boucle foreach pour itérer au sein de la liste et appelez la méthode Draw sur chaque objet Shape dans la liste. Bien que chaque objet dans la liste ait un type déclaré de Shape, c'est le type au moment de l'exécution (la version substituée de la méthode dans chaque classe dérivée) qui sera appelé.

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");
    }
}

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

class Program
{
    static void Main(string[] args)
    {
        // Polymorphism at work #1: a Rectangle, Triangle and Circle
        // can all be used whereever a Shape is expected. No cast is
        // required because an implicit conversion exists from a derived 
        // class to its base class.
        System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
        shapes.Add(new Rectangle());
        shapes.Add(new Triangle());
        shapes.Add(new Circle());

        // Polymorphism at work #2: the virtual method Draw is
        // invoked on each of the derived classes, not the base class.
        foreach (Shape s in shapes)
        {
            s.Draw();
        }

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

/* 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
 */

Dans C#, chaque type est polymorphe 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 gagne toutes les méthodes, champs, propriétés et événements de la classe de base. Le concepteur de la classe dérivée peut choisir s'il faut :

  • substituer les membres virtuels dans la classe de base ;

  • hériter de la méthode de classe de base la plus proche sans la substituer ;

  • définir une nouvelle implémentation non virtuelle des membres qui masquent les implémentations de la classe de base.

Une classe dérivée peut substituer un membre de classe de base uniquement si celui-ci est déclaré comme virtuel ou abstrait. Le membre dérivé doit utiliser le mot clé override pour indiquer explicitement que la méthode est destinée à participer à un appel virtuel. Le code suivant en est un 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. Lorsqu'une classe dérivée substitue un membre virtuel, ce dernier est appelé même lorsqu'une instance de cette classe fait l'objet d'un accès en tant qu'instance de la classe de base. Le code suivant en est un exemple :

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

BaseClass A = (BaseClass)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 devoir utiliser l'implémentation de la classe de base d'une méthode. Pour plus d'informations, consultez Versioning avec les mots clés override et new (Guide de programmation C#). Une interface offre un autre moyen de définir une méthode ou un ensemble de méthodes dont l'implémentation est laissée à des classes dérivées. Pour plus d'informations, consultez Interfaces (Guide de programmation C#) et Choix entre des classes et des interfaces.

Masquage des membres de la classe de base avec de nouveaux membres

Si vous souhaitez que votre membre dérivé ait le même nom qu'un membre dans une classe de base, mais vous ne souhaitez pas qu'il participe aux appels virtuels, vous pouvez utiliser le mot clé new. Le mot clé new est placé avant le type de retour d'un membre de classe qui est remplacé. Le code suivant en est un 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 la classe de base masqués sont tout de même accessibles à partir du code client en effectuant un cast de l'instance de la classe dérivée en 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 des classes dérivées de substituer des membres virtuels

Les membres virtuels restent toujours virtuels, quel que soit le nombre de classes qui ont été 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, que la classe B dérive de A, et que la classe C dérive de B, la classe C hérite du membre virtuel et a la possibilité de le remplacer, que la classe B ait déclaré une substitution pour ce membre ou non. Le code suivant en est un 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 sealed. Cela nécessite de placer le mot clé sealed avant le mot clé override dans la déclaration du membre de classe. Le code suivant en est un exemple :

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

Dans l'exemple précédent, la méthode DoWork n'est plus virtuelle à toute classe dérivée de C. Ceci est encore virtuel pour les instances de C, même si elles sont associées au type B ou au type A. Les méthodes sealed peuvent être remplacées par les classes dérivées à l'aide du mot clé new, comme l'exemple suivant le montre :

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

Dans ce cas, si DoWork est appelée sur D à l'aide d'une variable de type D, la nouvelle DoWork est appelée. 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 sur la classe C.

Accès à des membres virtuels de la classe de base à partir de classes dérivées

Une classe dérivée qui a remplacé ou a substitué une méthode ou propriété peut toujours accéder à la méthode ou à la propriété de la classe de base à l'aide du mot clé base. Le code suivant en est un 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 la base pour appeler l'implémentation de la classe de base de ce membre dans leur propre implémentation. Laisser le comportement de la classe de base se produire permet à la classe dérivée de se concentrer sur l'implémentation d'un comportement spécifique à la classe dérivée. Si l'implémentation de classe de base n'est pas appelée, c'est à la classe dérivée de rendre son comportement compatible avec le comportement de la classe de base.

Dans cette section

Voir aussi

Référence

Héritage (Guide de programmation C#)

Classes abstract et sealed et membres de classe (Guide de programmation C#)

Méthodes (guide de programmation C#)

Événements (Guide de programmation C#)

Propriétés (Guide de programmation C#)

Indexeurs (Guide de programmation C#)

Types (Guide de programmation C#)

Concepts

Guide de programmation C#

Guide de programmation C#