1. Introduzione
Immagina di avere un'app per la gestione degli animali allo zoo (proprio quell'esempio che abbiamo iniziato a sviluppare nelle lezioni precedenti). Abbiamo creato una classe base Animal con il metodo MakeSound, così che tutti gli animali possano "emettere" un suono.
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("L'animale emette un suono.");
}
}
Ma poi scopri che il leone e il pappagallo fanno suoni completamente diversi. Il generico "L'animale emette un suono" non va più bene. Ed è qui che entra in gioco l'override dei metodi. Vuoi che ogni discendente abbia il suo suono!
2. Sintassi: come funziona override
Regole principali
- Per fare override di un metodo, deve essere marcato nella classe base come virtual (o abstract).
- Nella classe derivata usi la parola chiave override prima del metodo con la stessa firma.
Esempio: leoni e pappagalli
public class Lion : Animal
{
public override void MakeSound()
{
Console.WriteLine("Rrrrrr!");
}
}
public class Parrot : Animal
{
public override void MakeSound()
{
Console.WriteLine("Polly è scemo!");
}
}
Ora, se crei una lista di animali e chiami MakeSound per ognuno, ognuno "parlerà" a modo suo:
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Animal()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
// Output:
// Rrrrrr!
// Polly è scemo!
// L'animale emette un suono.
3. Virtual table delle chiamate (v-table)
Quando usi virtual e override, il compilatore genera per la classe una speciale "tabella virtuale dei metodi" (v-table).
Quando chiami un metodo tramite un riferimento alla classe base, il CLR guarda nella tabella: questo metodo è stato overridato in un discendente? Se sì — viene chiamata la versione del discendente.
Questa è la "magia" del late binding (late binding), cioè il polimorfismo dinamico.
4. Mettiamo tutto insieme: continuiamo a sviluppare la nostra app
Aggiungiamo un'altra classe:
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Bau-bau!");
}
}
E usiamolo nella nostra mini-app di gestione animali:
Animal[] zoo = new Animal[]
{
new Lion(),
new Parrot(),
new Dog()
};
foreach (var animal in zoo)
{
animal.MakeSound();
}
Ora il codice è molto più estendibile e facile da mantenere, e aggiungere nuovi animali è un vero piacere!
5. Override delle proprietà e override con valore di ritorno
I metodi non sono l'unica cosa che puoi overridare. Puoi anche fare override di proprietà virtuali e indexer.
public class Animal
{
public virtual string Name { get; set; } = "Animale";
}
public class Lion : Animal
{
public override string Name { get; set; } = "Leone";
}
Questo è super comodo quando vuoi specificare info per una specie particolare.
6. Implementazione base con base
A volte vuoi estendere il comportamento del metodo base, non sostituirlo del tutto.
Per farlo, chiama l'implementazione base con la parola chiave base dentro l'override.
public class Parrot : Animal
{
public override void MakeSound()
{
base.MakeSound(); // "L'animale emette un suono."
Console.WriteLine("Polly è scemo!");
}
}
In questo caso il pappagallo prima farà il suono "da zoo", poi dirà il suo famoso "Polly è scemo!".
7. Utilità pratica: dove serve davvero
Capire come funziona override è fondamentale in qualsiasi progetto C# serio dove si usa tanto OOP.
- Modelli di animali nel nostro zoo? Già fatto.
- Creazione di controlli custom per la UI: Fai override dei metodi visuali standard e aggiungi la tua logica.
- Business logic flessibile: Ti permette di costruire lo "scheletro" del comportamento nelle classi base e mettere i dettagli nei discendenti.
- Testing e mock: Puoi facilmente "sostituire" i metodi tramite i discendenti per i test unitari.
- Plugin ed estensioni: Interfaccia o classe base astratta, tante implementazioni — e tutto funziona grazie all'override fatto bene.
8. sealed override e metodi virtuali nelle catene
Se non vuoi che qualcuno più in basso nella gerarchia faccia ancora override del tuo metodo overridato, puoi usare sealed override:
public class Base
{
public virtual void Foo() { }
}
public class Middle : Base
{
public sealed override void Foo() { }
}
public class Last : Middle
{
public override void Foo() {} // Errore — non puoi fare override!
}
Questo ti permette di "chiudere" la catena degli override dove è critico per il funzionamento corretto del sistema.
9. Nuovo metodo (parola chiave new)
A volte vuoi dichiarare in un discendente un metodo con la stessa firma, ma quello base non è virtual.
In questo caso puoi usare la parola chiave new — ma non è polimorfismo, è più una "mascherata":
public class Animal
{
public void MakeSound()
{
Console.WriteLine("Sono un animale!");
}
}
public class Cat : Animal
{
public new void MakeSound()
{
Console.WriteLine("Sono un gattino!");
}
}
Animal animal = new Cat();
animal.MakeSound(); // "Sono un animale!"
Cat cat = new Cat();
cat.MakeSound(); // "Sono un gattino!"
Qui decide tutto il tipo della variabile al momento della chiamata! Quindi per il vero polimorfismo dinamico usa sempre la combo virtual + override.
10. Errori tipici quando si fa override dei metodi
Errore n°1: tentare di fare override di un metodo che non è stato dichiarato come virtual, abstract o override.
In C# non puoi fare override dei metodi normali. Se il metodo nella classe base non ha un modificatore speciale (virtual, abstract o override), provare a scrivere override nella derivata dà errore di compilazione.
Errore n°2: firme dei metodi non corrispondenti.
Perché il metodo sia davvero overridato, nome, tipo di ritorno e parametri devono essere identici a quelli della base. Anche una piccola differenza (tipo parametro diverso) crea un nuovo metodo, non un override.
Errore n°3: hai dimenticato il modificatore override.
Se definisci un metodo con la stessa firma della base ma non metti override, il compilatore non lo considera un override. Si chiama nascondere il metodo (lo vedremo a parte). In questo caso, chiamando il metodo tramite una variabile del tipo base otterrai un risultato inaspettato — verrà chiamato il metodo base, non il tuo.
GO TO FULL VERSION