Condividi tramite


Ereditarietà: derivare i tipi per creare un comportamento più specializzato

L'ereditarietà, insieme all'incapsulamento e al polimorfismo, è una delle tre caratteristiche principali della programmazione orientata agli oggetti. L'ereditarietà consente di creare nuove classi che riutilizzano, estendono e modificano il comportamento definito in altre classi. La classe i cui membri vengono ereditati è denominata classe base e la classe che eredita tali membri è denominata classe derivata. Una classe derivata può avere una sola classe base diretta. Tuttavia, l'ereditarietà è transitiva. Se ClassC è derivato da ClassBe ClassB è derivato da ClassA, ClassC eredita i membri dichiarati in ClassB e ClassA.

Annotazioni

Gli struct non supportano l'ereditarietà, ma possono implementare interfacce.

Concettualmente, una classe derivata è una specializzazione della classe base. Ad esempio, se si dispone di una classe Animaldi base , si potrebbe avere una classe derivata denominata e un'altra classe derivata denominata MammalReptile. Un Mammal è un Animal e un Reptile è un Animal, ma ogni classe derivata rappresenta diverse specializzazioni della classe base.

Le dichiarazioni di interfaccia possono definire un'implementazione predefinita per i relativi membri. Queste implementazioni vengono ereditate dalle interfacce derivate e dalle classi che implementano tali interfacce. Per altre informazioni sui metodi di interfaccia predefiniti, vedere l'articolo sulle interfacce.

Quando si definisce una classe da derivare da un'altra classe, la classe derivata ottiene implicitamente tutti i membri della classe base, ad eccezione dei costruttori e dei finalizzatori. La classe derivata riutilizza il codice nella classe di base senza doverlo riapplicare. È possibile aggiungere altri membri nella classe derivata. La classe derivata estende la funzionalità della classe base.

La figura seguente mostra una classe WorkItem che rappresenta un elemento di lavoro in un processo aziendale. Come tutte le classi, deriva da System.Object e eredita tutti i relativi metodi. WorkItem aggiunge sei membri propri. Questi membri includono un costruttore, perché i costruttori non vengono ereditati. La classe ChangeRequest eredita da WorkItem e rappresenta un particolare tipo di elemento di lavoro. ChangeRequest aggiunge altri due membri ai membri che eredita da WorkItem e da Object. Deve aggiungere il proprio costruttore e, inoltre, aggiunge anche originalItemID. La proprietà originalItemID consente di associare l'istanza ChangeRequest all'originale WorkItem a cui si applica la richiesta di modifica.

Diagramma che mostra l'ereditarietà delle classi

Nell'esempio seguente viene illustrato il modo in cui le relazioni tra le classi illustrate nella figura precedente sono espresse in C#. L'esempio mostra anche come WorkItem esegue l'override del metodo Object.ToStringvirtuale e come la ChangeRequest classe eredita l'implementazione WorkItem del metodo . Il primo blocco definisce le classi:

// 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)
    {
        ID = GetNextID();
        Title = title;
        Description = desc;
        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;
    }
}

Questo blocco successivo illustra come usare le classi di base e derivate:

// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new("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("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
*/

Metodi astratti e virtuali

Quando una classe base dichiara un metodo come virtual, una classe derivata può override essere il metodo con la propria implementazione. Se una classe base dichiara un membro come abstract, tale metodo deve essere sottoposto a override in qualsiasi classe non astratta che eredita direttamente da tale classe. Se una classe derivata è astratta, eredita i membri astratti senza implementarli. I membri astratti e virtuali sono la base per il polimorfismo, che è la seconda caratteristica principale della programmazione orientata agli oggetti. Per altre informazioni, vedere Polimorfismo.

Classi di base astratte

È possibile dichiarare una classe come astratta se si vuole impedire la creazione diretta di istanze usando il nuovo operatore. Una classe astratta può essere usata solo se ne deriva una nuova classe. Una classe astratta può contenere una o più firme di metodo dichiarate come astratte. Queste firme specificano i parametri e il valore restituito, ma non hanno implementazione (corpo del metodo). Una classe astratta non deve contenere membri astratti; Tuttavia, se una classe contiene un membro astratto, la classe stessa deve essere dichiarata come astratta. Le classi derivate che non sono astratte devono fornire l'implementazione per qualsiasi metodo astratto da una classe di base astratta.

Interfacce

Un'interfaccia è un tipo riferimento che definisce un set di membri. Tutte le classi e gli struct che implementano tale interfaccia devono implementare tale set di membri. Un'interfaccia può definire un'implementazione predefinita per uno o tutti questi membri. Una classe può implementare più interfacce anche se può derivare da una sola classe di base diretta.

Le interfacce vengono usate per definire funzionalità specifiche per le classi che non hanno necessariamente una relazione "è un". Ad esempio, l'interfaccia System.IEquatable<T> può essere implementata da qualsiasi classe o struct per determinare se due oggetti del tipo sono equivalenti (tuttavia il tipo definisce l'equivalenza). IEquatable<T> non implica lo stesso tipo di relazione "is a" che esiste tra una classe di base e una classe derivata (ad esempio, una Mammal è una Animal). Per altre informazioni, vedere Interfacce.

Prevenzione di un'ulteriore derivazione

Una classe può impedire ad altre classi di ereditare da essa o da uno dei relativi membri dichiarando se stesso o il membro come sealed.

Classe derivata che nasconde i membri della classe base

Una classe derivata può nascondere i membri della classe base dichiarando membri con lo stesso nome e firma. Il new modificatore può essere usato per indicare in modo esplicito che il membro non è destinato a essere un override del membro di base. L'uso di new non è obbligatorio, ma viene generato un avviso del compilatore se new non viene usato. Per altre informazioni, vedere Controllo delle versioni con le parole chiave Override e New e Sapere quando usare override e nuove parole chiave.