Héritage et classes dérivées (C# et Java)

Mise à jour : novembre 2007

Vous pouvez étendre les fonctionnalités d'une classe existante en créant une classe nouvelle qui dérive de la classe existante. La classe dérivée hérite des propriétés de la classe de base, et vous pouvez ajouter ou substituer les méthodes et propriétés, selon les besoins.

En C#, l'héritage et l'implémentation d'interface sont définis par l'opérateur :, qui équivaut à extends et implements en Java. La classe de base doit toujours être à l'extrême gauche dans la déclaration de classe.

Comme Java, C# ne prend pas en charge l'héritage multiple, ce qui signifie que les classes ne peuvent pas hériter de plusieurs classes. Vous pouvez toutefois utiliser des interfaces dans ce but de la même manière qu'en Java.

Le code suivant définit une classe appelée CoOrds avec deux variables membres privées x et y qui représentent la position du point. Ces variables sont accédées par le biais de propriétés appelées respectivement X et Y :

public class CoOrds
{
    private int x, y;

    public CoOrds()  // constructor
    {
        x = 0;
        y = 0;
    }

    public int X
    {
        get { return x; }
        set { x = value; }
    }

    public int Y
    {
        get { return y; }
        set { y = value; }
    }
}

Vous dérivez une nouvelle classe, ColorCoOrds, à partir de la classe CoOrds, comme suit:

public class ColorCoOrds : CoOrds

ColorCoOrds hérite ensuite de tous les champs et méthodes de la classe de base, auxquels vous pouvez ajouter de nouveaux afin de fournir des fonctionnalités supplémentaires dans la classe dérivée, suivant vos besoins. Dans cet exemple, vous ajoutez un membre privé et des accesseurs pour ajouter de la couleur à la classe :

public class ColorCoOrds : CoOrds
{
    private System.Drawing.Color screenColor;


    public ColorCoOrds()  // constructor
    {
        screenColor = System.Drawing.Color.Red;
    }

    public System.Drawing.Color ScreenColor
    {
        get { return screenColor; }
        set { screenColor = value; }
    }
}

Le constructeur de la classe dérivée appelle implicitement le constructeur de la classe de base, ou superclasse, en terminologie Java. Avec l'héritage, tous les constructeurs de classe de base sont appelés avant les constructeurs de la classe dérivée dans l'ordre d'apparition des classes dans la hiérarchie des classes.

Effectuer un cast de type en classe de base

Comme en Java, vous ne pouvez pas utiliser de référence à une classe de base pour accéder même aux membres et méthodes d'une classe dérivée si la référence à la classe de base peut contenir une référence valide à un objet du type dérivé.

Vous pouvez implicitement référencer une classe dérivée avec une référence au type dérivé :

ColorCoOrds color1 = new ColorCoOrds();
CoOrds coords1 = color1;

Dans ce code, la référence à la classe de base, coords1, contient une copie de la référence color1.

Le mot clé base

Vous pouvez accéder aux membres de la classe de base dans une sous-classe même lorsque ces membres de base sont substitués dans la superclasse à l'aide du mot clé base. Par exemple, vous pouvez créer une classe dérivée qui contient une méthode portant la même signature que dans la classe de base. Si vous avez fait précéder cette méthode du mot clé new, vous indiquez qu'il s'agit d'une méthode toute nouvelle appartenant à la classe dérivée. Vous pourriez encore fournir une méthode pour accéder à la méthode d'origine dans la classe de base avec le mot clé base.

Supposons, par exemple, que votre classe de base CoOrds ait une méthode appelée Invert(), qui permute les coordonnées x et y. Vous pourriez fournir un substitut pour cette méthode dans votre classe ColorCoOrds dérivée avec un code comme ceci :

public new void Invert()
{
    int temp = X;
    X = Y;
    Y = temp;
    screenColor = System.Drawing.Color.Gray;
}

Comme vous pouvez le voir, cette méthode permute x et y, puis définit la couleur des coordonnées à gris. Vous pourriez fournir l'accès à l'implémentation de base pour cette méthode en créant une autre méthode dans ColorCoOrds, telle que celle-ci :

public void BaseInvert()
{
    base.Invert();
}

Vous appelez alors la méthode de base sur un objet ColorCoOrds en appelant la méthode BaseInvert().

ColorCoOrds color1 = new ColorCoOrds();
color1.BaseInvert();

Souvenez-vous que vous obtiendriez le même effet en assignant une référence à la classe de base à une instance de ColorCoOrds, puis en accédant à ses méthodes :

CoOrds coords1 = color1;
coords1.Invert();

Sélection de constructeurs

Les objets de classe de base sont toujours construits avant toute classe dérivée. Par conséquent, le constructeur de la classe de base est exécuté avant le constructeur de la classe dérivée. Si la classe de base a plusieurs constructeurs, la classe dérivée peut décider du constructeur à appeler. Par exemple, vous pourriez modifier votre classe CoOrds de sorte d'ajouter un deuxième constructeur, comme suit :

public class CoOrds
{
    private int x, y;

    public CoOrds()
    {
        x = 0;
        y = 0;
    }

    public CoOrds(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

Vous pourriez modifier ensuite la classe ColorCoOrds de sorte d'utiliser un constructeur particulier parmi ceux qui sont disponibles à l'aide du mot clé base :

public class ColorCoOrds : CoOrds
{
    public System.Drawing.Color color;

    public ColorCoOrds() : base ()
    {
        color = System.Drawing.Color.Red;
    }

    public ColorCoOrds(int x, int y) : base (x, y)
    {
        color = System.Drawing.Color.Red;
    }
}

En Java, cette fonctionnalité est implémentée à l'aide du mot clé super.

Substitution de méthode

Une classe dérivée peut substituer la méthode d'une classe de base en fournissant une nouvelle implémentation pour la méthode déclarée. Une différence importante entre Java et C# est que par défaut, les méthodes Java sont marquées comme virtuelles, tandis qu'en C#, les méthodes doivent être explicitement marquées comme virtuelles à l'aide du modificateur virtual. Les accesseurs de propriété, ainsi que les méthodes, peuvent être substitués d'une manière très similaire.

Méthodes virtuelles

Une méthode qui doit être substituée dans une classe dérivée est déclarée avec le modificateur virtual. Dans une classe dérivée, la méthode substituée est déclarée à l'aide du modificateur override.

Le modificateur override désigne une méthode ou une propriété d'une classe dérivée qui en remplace une portant le même nom et la même signature dans la classe de base. La méthode de base, qui doit être substituée, doit être déclarée comme virtual, abstract ou override : il n'est pas possible de substituer une méthode non virtuelle ou statique de cette manière. Qu'il s'agisse de la méthode ou propriété substituée ou de la méthode ou propriété de substitution, elles doivent avoir les mêmes modificateurs de niveau d'accès.

L'exemple suivant illustre une méthode virtuelle appelée StepUp qui est substituée dans une classe dérivée par le modificateur de substitution :

public class CountClass 
{
    public int count; 

    public CountClass(int startValue)  // constructor
    {
        count = startValue;
    }

    public virtual int StepUp()
    {
        return ++count;
    }
}

class Count100Class : CountClass 
{
    public Count100Class(int x) : base(x)  // constructor 
    {
    }

    public override int StepUp()
    {
        return ((base.count) + 100);
    }
}

class TestCounters
{
    static void Main()
    {
        CountClass counter1 = new CountClass(1);
        CountClass counter100 = new Count100Class(1);

        System.Console.WriteLine("Count in base class = {0}", counter1.StepUp());
        System.Console.WriteLine("Count in derived class = {0}", counter100.StepUp());
    } 
}

Lorsque vous exécutez ce code, vous voyez que le constructeur de la classe dérivée utilise le corps de la méthode fourni dans la classe de base, ce qui vous permet d'initialiser le membre "count" sans dupliquer ce code. Voici la sortie :

Count in base class = 2

Count in derived class = 101

Classes abstraites

Une classe abstraite déclare une ou plusieurs méthodes ou propriétés comme abstraites. Les méthodes de ce type n'ont pas d'implémentation fournie dans la classe qui les déclare, bien qu'une classe abstraite puisse également contenir des méthodes non abstraites, à savoir des méthodes pour lesquelles une implémentation a été fournie. Une classe abstraite ne peut pas être instanciée directement, mais uniquement comme une classe dérivée. Les classes dérivées de ce type doivent fournir des implémentations pour toutes les méthodes et propriétés abstraites, à l'aide du mot clé override, à moins que le membre dérivé soit lui-même déclaré abstrait.

L'exemple suivant déclare une classe Employee abstraite. Vous créez également une classe dérivée appelée Manager qui apporte une implémentation de la méthode Show() abstraite définie dans la classe Employee :

public abstract class Employee 
{ 
    protected string name;

    public Employee(string name)  // constructor
    { 
        this.name = name;
    }

    public abstract void Show();  // abstract show method
}

public class Manager: Employee
{ 
    public Manager(string name) : base(name) {}  // constructor

    public override void Show()  //override the abstract show method
    {
        System.Console.WriteLine("Name : " + name);
    }
}

class TestEmployeeAndManager
{ 
    static void Main()
    { 
        // Create an instance of Manager and assign it to a Manager reference:
        Manager m1 = new Manager("H. Ackerman");
        m1.Show();

        // Create an instance of Manager and assign it to an Employee reference:
        Employee ee1 = new Manager("M. Knott");
        ee1.Show();  //call the show method of the Manager class
    } 
}

Ce code appelle l'implémentation de la fonction Show() fournie par la classe Manager et affiche le nom de l'employé à l'écran. Voici la sortie :

Name : H. Ackerman

Name : M. Knott

Interfaces

Une interface est une sorte de classe squelette, qui contient des signatures de méthodes, mais aucune implémentation de méthode. De cette manière, les interfaces sont comme des classes abstraites qui contiennent uniquement des méthodes abstraites. Les interfaces C# sont très similaires aux interfaces Java, et fonctionnent en grande partie de la même manière.

Tous les membres d'une interface sont publics par définition, et une interface ne peut pas contenir de constantes, de champs (données membres privées), de constructeurs, de destructeurs ou de tout type de membre statique. Le compilateur générera une erreur si un modificateur est spécifié pour les membres d'une interface.

Vous pouvez dériver des classes à partir d'une interface pour implémenter cette interface. Les classes dérivées de ce type doivent fournir des implémentations pour toutes les méthodes de l'interface sauf si la classe dérivée est déclarée abstraite.

Une interface est déclarée de manière identique en Java. Dans une définition d'interface, une propriété indique uniquement son type et s'il est en lecture seule, en écriture seule ou en lecture/écriture par ses seuls mots clés get et set. L'interface ci-dessous déclare une propriété en lecture seule :

public interface ICDPlayer
{ 
    void Play();  // method signature
    void Stop();  // method signature

    int FastForward(float numberOfSeconds);

    int CurrentTrack  // read-only property
    {
        get;
    } 
}

Une classe peut hériter de cette interface à l'aide d'un signe deux-points à la place du mot clé implements de Java. La classe qui implémente doit fournir des définitions pour toutes les méthodes, ainsi que tous les accesseurs de propriété requis, comme suit :

public class CDPlayer : ICDPlayer
{
    private int currentTrack = 0;

    // implement methods defined in the interface
    public void Play()
    {
        // code to start CD...
    }

    public void Stop()
    {
        // code to stop CD...
    }

    public int FastForward(float numberOfSeconds)
    {
        // code to fast forward CD using numberOfSeconds...

        return 0;  //return success code
    }

    public int CurrentTrack  // read-only property
    { 
        get 
        { 
            return currentTrack; 
        } 
    }

    // Add additional methods if required...
}

Implémentation d'interfaces multiples

Une classe peut implémenter plusieurs interfaces à l'aide de la syntaxe suivante :

public class CDAndDVDComboPlayer : ICDPlayer, IDVDPlayer

Si une classe implémente plusieurs interfaces entre lesquelles existe une ambiguïté dans le nom des membres, le problème est résolu en utilisant le qualificateur complet du nom de la propriété ou de la méthode. En d'autres termes, la classe dérivée peut résoudre le conflit en utilisant le nom qualifié complet pour la méthode pour indiquer à quelle interface il appartient, comme dans ICDPlayer.Play().

Voir aussi

Concepts

Guide de programmation C#

Référence

Héritage (Guide de programmation C#)

Autres ressources

Langage de programmation C# pour les développeurs Java