Controllo delle versioni con le parole chiave Override e New (Guida per programmatori C#)

Il linguaggio C# è concepito in modo che le versioni di classi base e classi derivate presenti in librerie differenti possano essere aggiornate mantenendo la compatibilità con le versioni precedenti. Questo significa, ad esempio, che l'introduzione in una classe base di un nuovo membro con lo stesso nome di un membro di una classe derivata è completamente supportata e non determina comportamenti imprevisti. Significa inoltre che una classe deve indicare in modo esplicito se un metodo sostituisce un metodo ereditato oppure se è un nuovo metodo che nasconde un metodo ereditato con nome simile.

Nel linguaggio C# le classi derivate possono contenere metodi con lo stesso nome di quelli della classe base.

  • Il metodo della classe base deve essere definito come virtual.

  • Se il metodo della classe derivata non è preceduto dalla parola chiave new o override, in fase di compilazione verrà generato un messaggio di avviso e il metodo si comporterà come se fosse presente la parola chiave new.

  • Se il metodo della classe derivata è preceduto dalla parola chiave new, verrà definito come indipendente dal metodo della classe base.

  • Se il metodo della classe derivata è preceduto dalla parola chiave override, gli oggetti della classe derivata chiameranno tale metodo anziché quello della classe base.

  • È possibile chiamare il metodo della classe base dall'interno della classe derivata utilizzando la parola chiave base.

  • Le parole chiave override, virtual e new possono essere applicate anche a proprietà, indicizzatori ed eventi.

Per impostazione predefinita, i metodi C# non sono virtuali. Se un metodo è dichiarato come virtuale, qualsiasi classe che eredita tale metodo può implementarne una versione specifica. Per rendere virtuale un metodo, viene utilizzato il modificatore virtual nella dichiarazione del metodo della classe base. La classe derivata può quindi eseguire l'override del metodo virtuale della classe base mediante la parola chiave override oppure nascondere tale metodo mediante la parola chiave new. Se non viene specificata né override né new, in fase di compilazione verrà visualizzato un messaggio di avviso e il metodo della classe derivata nasconderà quello della classe base.

Per una dimostrazione pratica di questo comportamento, si supponga che la società A abbia creato una classe denominata GraphicsClass, che si decide di utilizzare nel programma che si sta sviluppando. Di seguito è riportata GraphicsClass:

class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
}

Questa classe viene utilizzata dalla società per cui si lavora ed è quindi possibile utilizzarla per derivare la propria classe, aggiungendo un nuovo metodo:

class YourDerivedGraphicsClass : GraphicsClass
{
    public void DrawRectangle() { }
}

L'applicazione sviluppata viene eseguita senza problemi finché la società A non rilascia una nuova versione di GraphicsClass, simile al codice seguente:

class GraphicsClass
{
    public virtual void DrawLine() { }
    public virtual void DrawPoint() { }
    public virtual void DrawRectangle() { }
}

La nuova versione di GraphicsClass contiene ora un metodo denominato DrawRectangle. Inizialmente non accade nulla. Il codice binario della nuova versione è infatti ancora compatibile con quello della versione precedente. Di conseguenza, l'eventuale applicazione software distribuita continuerà a funzionare, anche se nei sistemi viene installata la nuova classe. Le eventuali chiamate al metodo DrawRectangle continueranno a fare riferimento alla versione definita nella classe derivata.

Se tuttavia si ricompila l'applicazione utilizzando la nuova versione di GraphicsClass, viene visualizzato dal compilatore l'avviso CS0108. Nel messaggio viene indicato che è necessario definire il comportamento del metodo DrawRectangle nell'applicazione.

Se si desidera che il metodo esegua l'override del nuovo metodo della classe base, utilizzare la parola chiave override:

class YourDerivedGraphicsClass : GraphicsClass
{
    public override void DrawRectangle() { }
}

Se si specifica la parola chiave override, gli oggetti derivati da YourDerivedGraphicsClass utilizzeranno la versione di DrawRectangle della classe derivata. Gli oggetti derivati da YourDerivedGraphicsClass potranno comunque accedere alla versione di DrawRectangle della classe base utilizzando la parola chiave base:

base.DrawRectangle();

Se non si desidera che il metodo esegua l'override del nuovo metodo della classe base, si applicano le considerazioni riportate di seguito: Per evitare confusione tra i due metodi, è possibile rinominare il proprio metodo. Questa soluzione può richiedere tempo, è suscettibile di errori e di fatto è poco pratica in alcuni casi. Se tuttavia il progetto è di dimensioni relativamente ridotte, è possibile utilizzare le opzioni di refactoring di Visual Studio per rinominare il metodo. Per ulteriori informazioni, vedere Refactoring di classi e tipi (Progettazione classi).

In alternativa, è possibile evitare la visualizzazione del messaggio di avviso utilizzando la parola chiave new nella definizione della classe derivata:

class YourDerivedGraphicsClass : GraphicsClass
{
    public new void DrawRectangle() { }
}

La parola chiave new consente di indicare al compilatore che la definizione nella classe derivata nasconde quella contenuta nella classe base. Questo è il comportamento predefinito.

Override e selezione del metodo

Quando un metodo viene denominato in una classe, il compilatore C# sceglie il metodo più appropriato da chiamare nel caso in cui più metodi siano compatibili con la chiamata, ad esempio se sono disponibili due metodi con lo stesso nome ed esistono parametri compatibili con quello passato. Nel caso specifico, i seguenti metodi sarebbero compatibili:

public class Derived : Base
{
    public override void DoWork(int param) { }
    public void DoWork(double param) { }
}

Quando DoWork viene chiamato in un'istanza di Derived, il compilatore C# tenterà innanzitutto di rendere la chiamata compatibile con le versioni di DoWork originariamente dichiarate in Derived. I metodi di override non vengono considerati come dichiarati in una classe, ma sono nuove implementazioni di un metodo dichiarato in una classe base. Solo se non è possibile stabilire una corrispondenza tra la chiamata e un metodo originale in Derived il compilatore C# tenterà di associare la chiamata a un metodo di cui è stato eseguito l'override, con lo stesso nome e parametri compatibili. Di seguito è riportato un esempio.

int val = 5;
Derived d = new Derived();
d.DoWork(val);  // Calls DoWork(double).

Poiché la variabile val può essere convertita implicitamente in un valore double, in fase di compilazione verrà chiamato DoWork(double) anziché DoWork(int). È possibile ovviare a questo problema in due modi. Innanzitutto, evitare di dichiarare come virtuali nuovi metodi con lo stesso nome. In secondo luogo, indicare al compilatore C# di eseguire la chiamata al metodo virtuale facendo in modo che esegua la ricerca nell'elenco dei metodi della classe base tramite il casting dell'istanza di Derived in Base. Poiché il metodo è virtuale, verrà chiamata l'implementazione di DoWork(int) in Derived. Di seguito è riportato un esempio.

((Base)d).DoWork(val);  // Calls DoWork(int) on Derived.

Vedere anche

Riferimenti

Classi e struct (Guida per programmatori C#)

Metodi (Guida per programmatori C#)

Ereditarietà (Guida per programmatori C#)

Concetti

Guida per programmatori C#