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 greca che significa "multiforme" e dispone di 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 insiemi 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 virtualie 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 esecuzione dell'oggetto e richiama quell'override del metodo virtuale. Pertanto, nel codice sorgente è possibile chiamare un metodo su una classe base e lanciare l'esecuzione di una versione di una classe derivata del metodo.
I metodi virtuali consentono di utilizzare gruppi di oggetti correlati in modo uniforme. Supponiamo 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 utilizzare 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.
Utilizzare 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 chiamata Shapee classi derivate quali Rectangle, Circlee Triangle. Definire nella classe Shape un metodo virtuale chiamato Drawe 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, utilizzare un ciclo foreach per scorrere l'elenco e chiamare il metodo Draw su ogni oggetto Shape nell'elenco. Anche se ogni oggetto nell'elenco dispone di un tipo dichiarato di Shape, sarà richiamato il tipo in fase di esecuzione (la versione del metodo di cui è stato eseguito l'override in ogni classe derivata).
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.
Cenni preliminari 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. La finestra di progettazione 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 il membro della classe base è dichiarato come virtuale oastratto. Il membro derivato deve utilizzare la parola chiave override per indicare esplicitamente che il metodo deve partecipare in chiamata virtuale. Nel codice che segue 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 che segue 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 utilizzare l'implementazione della classe base di un metodo. Per ulteriori 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 ulteriori informazioni, vedere Interfacce (Guida per programmatori C#) e Scelta tra classi e interfacce.
Nascondere un membro di una classe base con nuovi membri
Se si desidera che il membro derivato abbia lo stesso nome di un membro in una classe di base, ma non si desidera che partecipi in chiamata virtuale, è possibile utilizzare la parola chiave new . La parola chiave new viene inserita prima del tipo restituito di un membro di classe che viene sostituito. Nel codice che segue 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. Di seguito è riportato un 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 che segue 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 che segue 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 utilizzando una variabile di tipo D, verrà chiamato il nuovo DoWork. Se per accedere a un'istanza di D viene utilizzata 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 utilizzando la parola chiave base. Nel codice che segue 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 ulteriori informazioni, vedere base.
Nota
Nell'implementazione dei membri virtuali è consigliabile l'utilizzo della parola chiave base per le chiamate all'implementazione della classe base di tali membri. In questo modo, nella classe derivata potrà essere definita solo l'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#)