Polimorfismo (Guida per programmatori C#)
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. In questo caso, il tipo dichiarato dell'oggetto non è più identico al tipo in fase di esecuzione.
Le classi base possono definire e implementare metodi virtuali e le classi derivate possono eseguirne l'override, ovvero ne forniscono una 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. Quindi, nel codice sorgente è possibile chiamare un metodo su una classe base, causando l'esecuzione di una versione di tale metodo definita in 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. In fase di compilazione non è possibile sapere i tipi specifici di forme che l'utente creerà. 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:
Creare una gerarchia di classi nella quale ogni classe della forma specifica deriva da una classe base comune.
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 List<Shape> e aggiungervi un cerchio, un triangolo e un rettangolo. 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 nella lista è dichiarato di tipo Shape, sarà il tipo della fase di esecuzione (la versione del metodo di cui è stato eseguito l'override in ogni classe derivata) che verrà richiamato.
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");
}
}
class Circle : Shape
{
public override void Draw()
{
// Code to draw a circle...
Console.WriteLine("Drawing a circle");
base.Draw();
}
}
class Rectangle : Shape
{
public override void Draw()
{
// Code to draw a rectangle...
Console.WriteLine("Drawing a rectangle");
base.Draw();
}
}
class Triangle : Shape
{
public override void Draw()
{
// Code to draw a triangle...
Console.WriteLine("Drawing a triangle");
base.Draw();
}
}
class Program
{
static void Main(string[] args)
{
// 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.
System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
shapes.Add(new Rectangle());
shapes.Add(new Triangle());
shapes.Add(new Circle());
// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (Shape s in shapes)
{
s.Draw();
}
// Keep the console open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* 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 sul polimorfismo
Membri virtuali
Quando una classe derivata eredita da una classe base, ottiene tutti i metodi, i campi, le proprietà e gli eventi della classe base. Il progettista della classe derivata può scegliere se:
eseguire l'override di membri virtuali nella classe base;
ereditare il metodo della classe di base più vicino senza eseguirne l'override;
definire la nuova implementazione non virtuale di quei membri che nascondono le implementazioni della classe 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 nella 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; }
}
}
A differenza di metodi, proprietà, eventi e indicizzatori, i campi non 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 = (BaseClass)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 (Guida per programmatori C#). Un'interfaccia fornisce un'altra modalità per definire un metodo o un insieme di metodi la cui implementazione è lasciata alle classi derivate. Per altre informazioni, vedere Interfacce (Guida per programmatori C#).
Nascondere un membro di una classe base con nuovi membri
Se si vuole che il membro derivato abbia lo stesso nome di un membro in una classe di base, ma non si vuole che partecipi in chiamata virtuale, è possibile usare la parola chiave new. 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; }
}
}
I membri nascosti della classe base sono comunque accessibili dal codice client se si esegue il cast di un'istanza della classe derivata in un'istanza della classe 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 di membri virtuali
I membri virtuali rimangono sempre tali, indipendentemente dal numero di classi dichiarate fra i membri virtuali e la classe in cui è stato originariamente dichiarato il membro virtuale. Se nella classe A è dichiarato un membro virtuale, la classe B deriva da A e la classe C deriva da B, la classe C erediterà il membro virtuale e potrà eseguirne l'override, indipendentemente dal fatto che nella classe B sia dichiarato un override per tale 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. A tale scopo, è necessario inserire la parola chiave sealed prima della parola chiave override nella dichiarazione del membro della classe. Nel codice seguente ne viene illustrato un esempio:
public class C : B
{
public sealed override void DoWork() { }
}
Nell'esempio riportato in precedenza, il metodo DoWork non è più virtuale per nessuna classe derivata da C. È ancora virtuale per le istanze di C, anche se ne è stato eseguito il cast nel tipo B o A. I metodi sealed possono essere sostituiti da classi derivate tramite la parola chiave new, come illustrato nell'esempio seguente:
public class D : C
{
public new void DoWork() { }
}
In questo caso, se viene chiamato DoWork su D usando una variabile di tipo D, verrà chiamato il nuovo DoWork. Se per accedere a un'istanza di D viene usata una variabile di tipo C, B o A, una chiamata a DoWork seguirà le regole dell'ereditarietà virtuale, indirizzando queste chiamate all'implementazione di DoWork sulla classe C.
Accedere a membri virtuali di una 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.
Argomenti della sezione
Controllo delle versioni con le parole chiave Override e New (Guida per programmatori C#)
Sapere quando utilizzare le parole chiave Override e New (Guida per programmatori C#)
Procedura: eseguire l'override del metodo ToString (Guida per programmatori C#)
Vedere anche
Riferimenti
Ereditarietà (Guida per programmatori C#)
Classi e membri delle classi astratte e sealed (Guida per programmatori C#)
Metodi (Guida per programmatori C#)
Eventi (Guida per programmatori C#)
Proprietà (Guida per programmatori C#)
Indicizzatori (Guida per programmatori C#)
Tipi (Guida per programmatori C#)