Héritage : dériver des types pour créer un comportement plus spécialisé

L’héritage, avec l’encapsulation et le polymorphisme, est l’une des trois principales caractéristiques de la programmation orientée objet. L’héritage vous permet de créer de nouvelles classes qui réutilisent, étendent et modifient le comportement défini dans d’autres classes. La classe dont les membres sont hérités porte le nom de classe de base et la classe qui hérite de ces membres porte le nom de classe dérivée. Une classe dérivée ne peut avoir qu’une seule classe de base directe. Toutefois, l’héritage est transitif. S’il ClassC est dérivé de ClassB, et ClassB est dérivé de ClassA, ClassC hérite des membres déclarés dans ClassB et ClassA.

Notes

Les structs ne prennent pas en charge l’héritage, mais ils peuvent implémenter les interfaces.

D’un point de vue conceptuel, une classe dérivée est une spécialisation de la classe de base. Par exemple, si vous avez une classe de base Animal, vous pouvez avoir une classe dérivée nommée Mammal et une autre classe dérivée nommée Reptile. Un Mammal est un Animal, et un Reptile est un Animal, mais chaque classe dérivée représente des spécialisations différentes de la classe de base.

Les déclarations d’interface peuvent définir une implémentation par défaut pour ses membres. Ces implémentations sont héritées par des interfaces dérivées et par des classes qui implémentent ces interfaces. Pour plus d’informations sur les méthodes d’interface par défaut, consultez l’article sur les interfaces.

Quand vous définissez une classe à dériver d’une autre classe, la classe dérivée obtient implicitement tous les membres de la classe de base, à l’exception de ses constructeurs et de ses finaliseurs. La classe dérivée réutilise le code dans la classe de base sans avoir à le réaffecter. Vous pouvez ajouter d’autres membres dans la classe dérivée. La classe dérivée étend les fonctionnalités de la classe de base.

L’illustration suivante montre une classe WorkItem qui représente un élément de travail dans un processus métier. Comme toutes les classes, elle dérive de System.Object et hérite de toutes ses méthodes. WorkItem ajoute six membres de son propre. Ces membres incluent un constructeur, car les constructeurs ne sont pas hérités. La classe ChangeRequest hérite de WorkItem et représente un type particulier d’élément de travail. ChangeRequest ajoute deux membres supplémentaires aux membres qu’elle hérite de WorkItem et de Object. Elle doit ajouter son propre constructeur et ajoute également originalItemID. La propriété originalItemID permet à l’instance ChangeRequest d’être associée au WorkItem d’origine auquel s’applique la demande de modification.

Diagram that shows class inheritance

L’exemple suivant montre comment les relations de classe de l’illustration précédente sont exprimées en langage C#. L’exemple montre également comment WorkItem substitue la méthode virtuelle Object.ToString, et comment la classe ChangeRequest hérite de l’implémentation WorkItem de la méthode. Le premier bloc définit les classes :

// WorkItem implicitly inherits from the Object class.
public class WorkItem
{
    // Static field currentID stores the job ID of the last WorkItem that
    // has been created.
    private static int currentID;

    //Properties.
    protected int ID { get; set; }
    protected string Title { get; set; }
    protected string Description { get; set; }
    protected TimeSpan jobLength { get; set; }

    // Default constructor. If a derived class does not invoke a base-
    // class constructor explicitly, the default constructor is called
    // implicitly.
    public WorkItem()
    {
        ID = 0;
        Title = "Default title";
        Description = "Default description.";
        jobLength = new TimeSpan();
    }

    // Instance constructor that has three parameters.
    public WorkItem(string title, string desc, TimeSpan joblen)
    {
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = joblen;
    }

    // Static constructor to initialize the static member, currentID. This
    // constructor is called one time, automatically, before any instance
    // of WorkItem or ChangeRequest is created, or currentID is referenced.
    static WorkItem() => currentID = 0;

    // currentID is a static field. It is incremented each time a new
    // instance of WorkItem is created.
    protected int GetNextID() => ++currentID;

    // Method Update enables you to update the title and job length of an
    // existing WorkItem object.
    public void Update(string title, TimeSpan joblen)
    {
        this.Title = title;
        this.jobLength = joblen;
    }

    // Virtual method override of the ToString method that is inherited
    // from System.Object.
    public override string ToString() =>
        $"{this.ID} - {this.Title}";
}

// ChangeRequest derives from WorkItem and adds a property (originalItemID)
// and two constructors.
public class ChangeRequest : WorkItem
{
    protected int originalItemID { get; set; }

    // Constructors. Because neither constructor calls a base-class
    // constructor explicitly, the default constructor in the base class
    // is called implicitly. The base class must contain a default
    // constructor.

    // Default constructor for the derived class.
    public ChangeRequest() { }

    // Instance constructor that has four parameters.
    public ChangeRequest(string title, string desc, TimeSpan jobLen,
                         int originalID)
    {
        // The following properties and the GetNexID method are inherited
        // from WorkItem.
        this.ID = GetNextID();
        this.Title = title;
        this.Description = desc;
        this.jobLength = jobLen;

        // Property originalItemId is a member of ChangeRequest, but not
        // of WorkItem.
        this.originalItemID = originalID;
    }
}

Ce bloc suivant montre comment utiliser les classes de base et dérivées :

// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
                            "Fix all bugs in my code branch",
                            new TimeSpan(3, 4, 0, 0));

// Create an instance of ChangeRequest by using the constructor in
// the derived class that takes four arguments.
ChangeRequest change = new ChangeRequest("Change Base Class Design",
                                        "Add members to the class",
                                        new TimeSpan(4, 0, 0),
                                        1);

// Use the ToString method defined in WorkItem.
Console.WriteLine(item.ToString());

// Use the inherited Update method to change the title of the
// ChangeRequest object.
change.Update("Change the Design of the Base Class",
    new TimeSpan(4, 0, 0));

// ChangeRequest inherits WorkItem's override of ToString.
Console.WriteLine(change.ToString());
/* Output:
    1 - Fix Bugs
    2 - Change the Design of the Base Class
*/

Méthodes abstraites et virtuelles

Lorsqu’une classe de base déclare une méthode en tant que virtual, une classe dérivée peut override la méthode avec sa propre implémentation. Si une classe de base déclare un membre en tant que abstract, cette méthode doit être substituée dans toute classe non abstraite qui hérite directement de cette classe. Si une classe dérivée est abstraite, elle hérite des membres abstraits sans les implémenter. Les membres virtuels et abstraits sont la base du polymorphisme, qui est la deuxième caractéristique principale de la programmation orientée objet. Pour plus d’informations, consultez Polymorphisme.

Classes de base abstraites

Pour empêcher l’instanciation directe, vous pouvez déclarer une classe comme étant abstract à l’aide de l’opérateur new. Une classe abstraite ne peut être utilisée que si une nouvelle classe est dérivée de celle-ci. Une classe abstraite peut contenir une ou plusieurs signatures de méthode qui sont également déclarées comme abstraites. Ces signatures spécifient les paramètres et la valeur de retour, mais n’ont aucune implémentation (corps de méthode). Une classe abstraite n’a pas besoin de contenir de membres abstraits ; Toutefois, si une classe contient un membre abstrait, la classe elle-même doit être déclarée comme abstraite. Les classes dérivées qui ne sont pas abstraites elles-mêmes doivent fournir l’implémentation pour toutes les méthodes abstraites d’une classe de base abstraite.

Interfaces

Une interface est un type référence qui définit un ensemble de membres. Toutes les classes et structs qui implémentent cette interface doivent implémenter cet ensemble de membres. Une interface peut définir une implémentation par défaut pour l’un ou l’ensemble de ces membres. Une classe peut implémenter plusieurs interfaces, même si elle ne peut dériver que d’une seule classe de base directe.

Les interfaces sont utilisées pour définir des fonctionnalités spécifiques pour les classes qui n’ont pas nécessairement de relation « est une ». Par exemple, l’interface System.IEquatable<T> peut être implémentée par n’importe quelle classe ou struct pour déterminer si deux objets du type sont équivalents (toutefois, le type définit l’équivalence). IEquatable<T> n’implique pas le même type de relation « est une » qui existe entre une classe de base et une classe dérivée (par exemple, un Mammal est un Animal). Pour plus d'informations, consultez Interfaces.

Prévention d’une dérivation supplémentaire

Une classe peut empêcher d’autres classes d’hériter de celle-ci, ou de l’un de ses membres, en déclarant elle-même ou le membre en tant que sealed.

Masquage de classe dérivée des membres de classe de base

Une classe dérivée peut masquer des membres de la classe de base en déclarant les membres à l’aide du même nom et de la même signature. Le new modificateur peut être utilisé pour indiquer explicitement que le membre n’est pas destiné à être un remplacement du membre de base. L’utilisation n’est new pas obligatoire, mais un avertissement du compilateur est généré s’il new n’est pas utilisé. Pour plus d’informations, consultez Gestion de version avec les mots clés override et new et Savoir quand utiliser les mots clés override et new.