Ereditarietà e classi derivate (confronto tra C# e Java)

Aggiornamento: novembre 2007

È possibile estendere la funzionalità di una classe esistente creando una nuova classe che deriva da essa. La classe derivata eredita le proprietà della classe base ed è possibile aggiungere o eseguire l'override di metodi e proprietà secondo le necessità.

In C# sia l'ereditarietà che l'implementazione delle interfacce vengono definite dall'operatore :, equivalente a extends e implements di Java. La classe base deve trovarsi sempre all'estrema sinistra nella dichiarazione della classe.

Analogamente a Java, C# non supporta l'ereditarietà multipla, ossia le classi non possono ereditare da più di una classe. È tuttavia possibile utilizzare le interfacce per questo scopo, esattamente come in Java.

Nel codice riportato di seguito viene definita una classe denominata CoOrds con due variabili membro private x e y che rappresentano la posizione del punto. Queste variabili sono accessibili mediante proprietà denominate rispettivamente X e 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; }
    }
}

È possibile derivare una nuova classe, denominata ColorCoOrds, dalla classe CoOrds, come illustrato di seguito:

public class ColorCoOrds : CoOrds

ColorCoOrds eredita quindi dalla classe base tutti i campi e i metodi, a cui è possibile aggiungerne di nuovi per fornire funzionalità aggiuntive nella classe derivata, secondo le proprie esigenze. In questo esempio vengono aggiunti un membro privato e funzioni di accesso per aggiungere un colore alla 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; }
    }
}

Il costruttore della classe derivata chiama in modo implicito quello della classe base, o superclasse secondo la terminologia Java. Nell'ereditarietà tutti i costruttori della classe base vengono chiamati prima di quelli della classe derivata, nell'ordine di visualizzazione della gerarchia delle classi.

Cast del tipo in una classe base

Come in Java, non è possibile utilizzare un riferimento a una classe base per accedere ai membri e ai metodi di una classe derivata, anche se il riferimento alla classe base può contenere un riferimento valido a un oggetto del tipo derivato.

È possibile fare riferimento in modo implicito a una classe derivata con un riferimento al tipo derivato:

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

In questo codice il riferimento alla classe base, coords1, contiene una copia del riferimento color1.

Parola chiave base

È possibile accedere ai membri della classe base in una sottoclasse utilizzando la parola chiave base anche quando tali membri base sono sottoposti a override nella superclasse. È ad esempio possibile creare una classe derivata contenente un metodo con la stessa firma della classe base. Anteponendo al metodo la parola chiave new si specifica che si tratta di un metodo completamente nuovo appartenente alla classe derivata. È comunque possibile fornire un metodo per accedere al metodo originario nella classe base con la parola chiave base.

Si prenda come esempio una classe base CoOrds che dispone di un metodo denominato Invert() per l'inversione delle coordinate x e y. È possibile fornire un sostituto per questo metodo nelle classe derivata ColorCoOrds utilizzando il codice riportato di seguito:

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

Questo metodo inverte le coordinate x e y, quindi ne imposta il colore su grigio. È possibile fornire l'accesso all'implementazione base di questo metodo creando un altro metodo in ColorCoOrds, come quello riportato di seguito:

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

Richiamare quindi il metodo base su un oggetto ColorCoOrds mediante una chiamata al metodo BaseInvert().

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

Tenere presente che si ottiene lo stesso effetto assegnando un riferimento alla classe base a un'istanza di ColorCoOrds e quindi accedendo ai relativi metodi:

CoOrds coords1 = color1;
coords1.Invert();

Scelta dei costruttori

Gli oggetti della classe base vengono costruiti sempre prima delle eventuali classi che derivano da essi. Il costruttore della classe base viene pertanto eseguito prima di quello della classe derivata. Se la classe base ha più costruttori, la classe derivata può decidere quale costruttore chiamare. È ad esempio possibile modificare la classe CoOrds per aggiungere un secondo costruttore, come illustrato di seguito:

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

È quindi possibile modificare la classe ColorCoOrds per utilizzare uno dei costruttori disponibili ricorrendo alla parola chiave 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;
    }
}

In Java questa funzionalità viene implementata utilizzando la parola chiave super.

Override dei metodi

Una classe derivata può eseguire l'override del metodo di una classe base fornendo una nuova implementazione del metodo dichiarato. Java e C# differiscono tra loro per una caratteristica importante. I metodi Java sono contrassegnati come virtuali per impostazione predefinita, mentre i metodi C# devono essere contrassegnati come virtuali in modo esplicito utilizzando il modificatore virtual. Le funzioni di accesso alle proprietà, così come i metodi, possono essere sottoposte a override in modo analogo.

Metodi virtuali

Un metodo che deve essere sottoposto a override in una classe derivata viene dichiarato con il modificatore virtual. In una classe derivata il metodo sottoposto a override viene dichiarato utilizzando il modificatore override.

Il modificatore override indica un metodo o una proprietà di una classe derivata che sostituisce un metodo o una proprietà con lo stesso nome e la stessa firma nella classe base. Il metodo base, che deve essere sottoposto a override, deve essere dichiarato come virtual, abstract o override. Non è possibile eseguire in questo modo l'override di un metodo non virtuale o statico. Il metodo (o la proprietà) sottoposto a override e quello che esegue l'override devono avere gli stessi modificatori a livello di accesso.

Nell'esempio riportato di seguito viene illustrato un metodo virtuale, denominato StepUp, sottoposto a override in una classe derivata con il modificatore di override:

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

Quando si esegue questo codice, è possibile notare che il costruttore della classe derivata utilizza il corpo del metodo fornito nella classe base, consentendo di inizializzare il membro count senza duplicare il codice. L'output è il seguente:

Count in base class = 2

Count in derived class = 101

Classi astratte

Una classe astratta dichiara come astratti uno o più metodi o proprietà. Per questi metodi non viene fornita un'implementazione nella classe che li dichiara, benché una classe astratta possa contenere anche metodi non astratti, ossia metodi per i quali è stata fornita un'implementazione. Non è possibile creare un'istanza di una classe astratta direttamente, ma solo come classe derivata. Queste classi derivate devono fornire le implementazioni per tutti i metodi e le proprietà astratti tramite la parola chiave override, a meno che il membro derivato non sia esso stesso dichiarato come astratto.

Nell'esempio riportato di seguito viene dichiarata una classe Employee astratta. Viene inoltre creata una classe derivata, denominata Manager, che fornisce un'implementazione del metodo Show() astratto definito nella 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
    } 
}

Questo codice richiama l'implementazione di Show() fornita dalla classe Manager e visualizza il nome del dipendente. L'output è il seguente:

Name : H. Ackerman

Name : M. Knott

Interfacce

Un'interfaccia è una sorta di classe scheletro che contiene le firme dei metodi, senza le relative implementazioni. Le interfacce sono pertanto simili a classi astratte contenenti solo metodi astratti. Le interfacce C# sono molto simili e funzionano in modo analogo a quelle Java.

Tutti i membri di un'interfaccia sono pubblici per definizione. Inoltre, un'interfaccia non può contenere costanti, campi (membri dati privati), costruttori, distruttori o qualsiasi tipo di membro statico. Se viene specificato un modificatore per i membri di un'interfaccia, il compilatore genererà un errore.

È possibile derivare classi da un'interfaccia per implementare quest'ultima. Queste classi derivate devono fornire implementazioni per tutti i metodi dell'interfaccia, a meno che non vengano dichiarate astratte.

In Java e C# le interfacce vengono dichiarate nello stesso modo. In una definizione di interfaccia, per una proprietà vengono indicati solo il tipo e la caratteristica in sola lettura, sola scrittura o lettura/scrittura mediante le semplici parole chiave get e set. L'interfaccia riportata di seguito dichiara una proprietà in sola lettura:

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

    int FastForward(float numberOfSeconds);

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

Una classe può ereditare da questa interfaccia utilizzando i due punti anziché la parola chiave implements di Java. La classe di implementazione deve fornire le definizioni per tutti i metodi, nonché le eventuali funzioni di accesso alle proprietà, come illustrato di seguito:

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...
}

Implementazione di più interfacce

Una classe può implementare più interfacce utilizzando la sintassi riportata di seguito:

public class CDAndDVDComboPlayer : ICDPlayer, IDVDPlayer

Se una classe implementa più interfacce, eventuali ambiguità nei nomi dei membri vengono risolte utilizzando il qualificatore completo per il nome della proprietà o del metodo. In altri termini, la classe derivata può risolvere il conflitto utilizzando il nome completo del metodo per indicare a quale interfaccia appartiene, come in ICDPlayer.Play().

Vedere anche

Concetti

Guida per programmatori C#

Riferimenti

Ereditarietà (Guida per programmatori C#)

Altre risorse

Linguaggio di programmazione C# per sviluppatori Java