Polimorfismo a compile time e runtime
Fa parte della sezione Programmazione orientata agli oggetti del percorso C# di Coddy. Lezione 25 di 70.
Il polimorfismo significa «molte forme»: la capacità dello stesso codice di comportarsi diversamente a seconda del contesto. C# supporta due tipi distinti di polimorfismo e capire quando viene risolto ciascuno ti aiuta a scrivere codice più flessibile.
Il polimorfismo in fase di compilazione (chiamato anche polimorfismo statico) viene risolto dal compilatore prima che il programma venga eseguito. L'overload dei metodi è l'esempio principale: più metodi condividono lo stesso nome ma hanno parametri diversi:
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
}
var calc = new Calculator();
calc.Add(5, 3); // Il compilatore sceglie la versione int
calc.Add(5.0, 3.0); // Il compilatore sceglie la versione doubleIl compilatore esamina i tipi degli argomenti e seleziona il metodo corretto in fase di compilazione. Questa decisione viene presa prima ancora che il programma venga eseguito.
Il polimorfismo a runtime (polimorfismo dinamico) viene risolto mentre il programma è in esecuzione. Questo avviene con i metodi virtual e override: il tipo effettivo dell’oggetto determina quale metodo viene eseguito:
Animal pet = new Dog(); // Dichiarato come Animal, ma in realtà è un Dog
pet.Speak(); // Chiama Speak() di Dog durante l'esecuzioneIl compilatore non sa quale Speak() verrà eseguito: la decisione viene presa in fase di esecuzione in base al tipo effettivo dell'oggetto. È questo che rende il polimorfismo basato sull'ereditarietà così potente per creare sistemi flessibili ed estensibili.
Sfida
FacileCostruiamo un sistema di conversione che dimostri entrambi i tipi di polimorfismo in azione. Creerai una classe che usa l’overload dei metodi (polimorfismo in fase di compilazione) insieme a una gerarchia di ereditarietà che usa metodi virtuali/override (polimorfismo in fase di esecuzione).
Organizzerai il codice in quattro file:
Converter.cs: definisci una classeConverternello spazio dei nomiConversion. Questa classe dimostra il polimorfismo in fase di compilazione tramite l’overload dei metodi. Crea tre metodiConvertsovraccaricati:Convert(int value)restituisce"Integer: {value}"Convert(double value)restituisce"Double: {value}"Convert(string value)restituisce"String: {value}"
Shape.cs: definisci una classe baseShapenello spazio dei nomiConversion. Questa classe ha una proprietàName(stringa) e un costruttore che la imposta. Includi un metodovirtualchiamatoDescribe()che restituisce"This is a {Name}".Circle.cs: definisci una classeCirclenello spazio dei nomiConversionche eredita daShape. Aggiungi una proprietàRadius(double). Il costruttore accetta un raggio e passa"Circle"al costruttore della classe base. Esegui l’override diDescribe()affinché restituisca"This is a Circle with radius {Radius}".Program.cs: nel file principale, dimostra entrambi i tipi di polimorfismo. Per prima cosa, crea unConvertere chiama tutti e tre i metodi sovraccaricati con i valori di input. Poi crea unCircle, memorizzalo in una variabileShapee chiamaDescribe()per mostrare il polimorfismo in fase di esecuzione.
Riceverai quattro input:
- Un valore intero
- Un valore double
- Un valore stringa
- Il raggio di un cerchio (double)
Stampa l’output in questo formato:
Compile-time Polymorphism:
{Convert(int) result}
{Convert(double) result}
{Convert(string) result}
Runtime Polymorphism:
{Describe() result from Shape variable holding Circle}Per esempio, se gli input sono 42, 3.14, Hello e 5.5, l’output dovrebbe essere:
Compile-time Polymorphism:
Integer: 42
Double: 3.14
String: Hello
Runtime Polymorphism:
This is a Circle with radius 5.5Nota la differenza fondamentale: il compilatore decide quale metodo Convert chiamare in base ai tipi degli argomenti in fase di compilazione, mentre il metodo Describe() viene risolto in fase di esecuzione in base al tipo effettivo dell’oggetto, anche se è memorizzato in una variabile Shape!
Provalo tu
using System;
using Conversion;
class Program
{
public static void Main(string[] args)
{
// Leggi gli input
int intValue = Convert.ToInt32(Console.ReadLine());
double doubleValue = Convert.ToDouble(Console.ReadLine());
string stringValue = Console.ReadLine();
double radius = Convert.ToDouble(Console.ReadLine());
// TODO: Dimostra il polimorfismo a tempo di compilazione
// Crea un oggetto Converter e chiama tutte e tre le versioni sovraccaricate del metodo Convert
Console.WriteLine("Compile-time Polymorphism:");
// Chiama Convert con intValue, doubleValue e stringValue
// TODO: Dimostra il polimorfismo a runtime
// Crea un oggetto Circle e memorizzalo in una variabile Shape
// Poi chiama Describe() sulla variabile Shape
Console.WriteLine("Runtime Polymorphism:");
// Crea un oggetto Circle e chiama Describe()
}
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniNamespace e direttiveIntroduzione a classi e oggettiLa parola chiave "this"Metodi e parametriCampi e proprietàCostruttoriInizializzatori di oggettiRiepilogo - Calcolatrice semplice4Ereditarietà
Sintassi di base dell'ereditarietà (:)La parola chiave 'base'Parole chiave virtual e overrideClassi sealedLa classe base 'object'Riepilogo - Gerarchia dei dipendenti7Funzionalità avanzate
Sovraccarico degli operatoriIndicizzatori (this[])Override di ToString()Metodi di estensioneRiepilogo - Lista personalizzata10Pattern di progettazione Parte 1
Introduzione ai pattern di progettazioneSingleton thread-safePattern FactoryPattern Observer (eventi)Pattern Strategy2Proprietà e membri statici
Proprietà implementate automaticamenteProprietà di sola lettura e di sola scritturaCampi e metodi staticiClassi staticheMembri con corpo di espressione5Polimorfismo e interfacce
Polimorfismo a compile time e runtimeInterfaccia o classe astrattaInterfacce multipleInterfacce espliciteUpcasting e downcastingRipasso: calcolatore di forme8Concetti avanzati di programmazione a oggetti
Composizione invece dell'ereditarietàTipi generici (classi e metodi)Delegati ed eventiAttributi e reflectionIDisposable e istruzione usingNozioni di base sull'iniezione delle dipendenze11Pattern di progettazione, parte 2
Pattern del ComandoPattern dell’AdattatorePattern del DecoratorePattern Template MethodPattern dello StatoPattern Composito3Architettura delle classi
Dati di istanza vs staticiParole chiave 'readonly' e 'const'Campi di supportoRiepilogo - Gestore di conti bancari6Incapsulamento
Modificatori di accessoProprietà per l'incapsulamentoImplementazione dell'occultamento dei datiModelli di immutabilitàRiepilogo - Registri degli studentiEsercitati da solo: Compilatore C# online