Polimorfismo

Il polimorfismo è spesso definito il terzo pilastro della programmazione orientata a oggetti, dopo l'incapsulamento e l'ereditarietà. Polimorfismo è una parola che deriva dal greco e significa "multiforme". Il polimorfismo presenta due aspetti distinti:

  • In fase di esecuzione, oggetti di una classe derivata possono essere trattati come oggetti di una classe base in posizioni quali parametri del metodo e raccolte o matrici. Quando si verifica questo polimorfismo, il tipo dichiarato dell'oggetto non è più identico al tipo in fase di esecuzione.
  • Le classi di base possono definire e implementare metodi virtuali e le classi derivate possono eseguirne l'override, ovvero forniscono la propria definizione e implementazione. Durante la fase di esecuzione, quando il codice client chiama il metodo, CLR cerca il tipo in fase di esecuzione dell'oggetto e richiama quell'override del metodo virtuale. Nel codice sorgente è possibile chiamare un metodo su una classe di base e determinare l'esecuzione della versione del metodo di una classe derivata.

I metodi virtuali consentono di usare gruppi di oggetti correlati in modo uniforme. Si supponga ad esempio di avere un'applicazione di disegno che consenta a un utente di creare vari tipi di forme in un'area di disegno. Non si sa in fase di compilazione quali tipi specifici di forme verranno creati dall'utente. L'applicazione deve tuttavia tenere traccia di tutti i vari tipi di forme create e deve aggiornarli in risposta alle azioni del mouse dell'utente. È possibile usare il polimorfismo per risolvere questo problema in due passaggi di base:

  1. Creare una gerarchia di classi nella quale ogni classe della forma specifica deriva da una classe base comune.
  2. Usare un metodo virtuale per richiamare il metodo adatto su qualsiasi classe derivata tramite una sola chiamata al metodo della classe base.

Prima di tutto, creare una classe base denominata Shape e delle classi derivate quali Rectangle, Circle e Triangle. Definire nella classe Shape un metodo virtuale denominato Draw ed eseguirne l'override in ogni classe derivata per disegnare la particolare forma che la classe rappresenta. Creare un oggetto e aggiungerne uno List<Shape> , e Circle Triangle Rectangle .

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

public class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
public class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
public class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

Per aggiornare l'area di disegno, usare un ciclo foreach per scorrere l'elenco e chiamare il metodo Draw su ogni oggetto Shape nell'elenco. Anche se ogni oggetto nell'elenco ha un tipo dichiarato di , è il tipo di run-time (la versione sottoposta a override del metodo in ogni classe derivata) che Shape verrà richiamato.

// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
    new Rectangle(),
    new Triangle(),
    new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
    shape.Draw();
}
/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
*/

In C# ogni tipo è polimorfico perché tutti i tipi, incluso i tipi definiti dall'utente, ereditano da Object.

Panoramica del polimorfismo

Membri virtuali

Quando una classe derivata eredita da una classe di base, ottiene tutti i metodi, i campi, le proprietà e gli eventi della classe di base. La finestra di progettazione della classe derivata ha diverse opzioni per il comportamento dei metodi virtuali:

  • La classe derivata può eseguire l'override dei membri virtuali nella classe di base, definendo un nuovo comportamento.
  • La classe derivata eredita il metodo della classe base più vicino senza eseguirne l'override, mantenendo il comportamento esistente ma consentendo ad altre classi derivate di eseguire l'override del metodo.
  • La classe derivata può definire una nuova implementazione non virtuale di tali membri che nascondono le implementazioni della classe di base.

Una classe derivata può eseguire l'override di un membro della classe base solo se quest'ultimo è dichiarato come virtuale o astratto. Il membro derivato deve usare la parola chiave override per indicare esplicitamente che il metodo deve partecipare alla chiamata virtuale. Nel codice seguente ne viene illustrato un esempio:

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

I campi non possono essere virtuali. solo i metodi, le proprietà, gli eventi e gli indicizzatori possono essere virtuali. Quando una classe derivata esegue l'override di un membro virtuale, quest'ultimo viene chiamato anche nel caso in cui si acceda a un'istanza di tale classe come istanza della classe base. Nel codice seguente ne viene illustrato un esempio:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = B;
A.DoWork();  // Also calls the new method.

Metodi virtuali e proprietà consentono alle classi derivate di estendere una classe base senza dover usare l'implementazione della classe base di un metodo. Per altre informazioni, vedere Controllo delle versioni con le parole chiave Override e New. Un'interfaccia fornisce un'altra modalità per definire un metodo o un insieme di metodi la cui implementazione è lasciata alle classi derivate.

Nascondere i membri della classe base con nuovi membri

Se si vuole che la classe derivata abbia un membro con lo stesso nome di un membro in una classe di base, è possibile usare la nuova parola chiave per nascondere il membro della classe base. La parola chiave new viene inserita prima del tipo restituito di un membro di classe che viene sostituito. Nel codice seguente ne viene illustrato un esempio:

public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}

È possibile accedere ai membri nascosti della classe base dal codice client eseguendo il cast dell'istanza della classe derivata a un'istanza della classe di base. Ad esempio:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

Impedire alle classi derivate di eseguire l'override dei membri virtuali

I membri virtuali rimangono virtuali, indipendentemente dal numero di classi dichiarate tra il membro virtuale e la classe che lo ha originariamente dichiarato. Se la classe dichiara un membro virtuale e la classe deriva da e la classe deriva da , la classe eredita il membro virtuale e può eseguirne A l'override, indipendentemente dal fatto che la classe dichiari o meno un override per B A tale C B C B membro. Nel codice seguente ne viene illustrato un esempio:

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

Una classe derivata può interrompere l'ereditarietà virtuale dichiarando un override come sealed. Per arrestare l'ereditarietà sealed è necessario inserire la parola chiave prima della parola chiave nella dichiarazione del membro della override classe. Nel codice seguente ne viene illustrato un esempio:

public class C : B
{
    public sealed override void DoWork() { }
}

Nell'esempio precedente il metodo DoWork non è più virtuale per le classi derivate da C . È ancora virtuale per le istanze di , anche se ne viene eseguito il C cast al tipo o al tipo B A . I metodi sealed possono essere sostituiti da classi derivate usando la new parola chiave , come illustrato nell'esempio seguente:

public class D : C
{
    public new void DoWork() { }
}

In questo caso, se DoWork viene chiamato su usando una variabile di tipo , viene chiamato il nuovo D D DoWork . Se una variabile di tipo , o viene usata per accedere a un'istanza di , una chiamata a seguirà le regole di ereditarietà virtuale, indirizzando tali chiamate all'implementazione di C B nella classe A D DoWork DoWork C .

Accedere ai membri virtuali della classe base dalle classi derivate

Una classe derivata che ha sostituito un metodo o una proprietà, o ne ha eseguito l'override, può ancora accedere al metodo o alla proprietà sulla classe base usando la parola chiave base. Nel codice seguente ne viene illustrato un esempio:

public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}

Per altre informazioni, vedere base.

Nota

Nell'implementazione dei membri virtuali è consigliabile l'uso della parola chiave base per le chiamate all'implementazione della classe base di tali membri. In questo modo, nella classe derivata sarà possibile definire la sola implementazione del comportamento specifico per tale classe. Se l'implementazione della classe base non viene chiamata, spetterà alla classe derivata rendere il proprio comportamento compatibile con quello della classe base.