1. Introduzione
In questa lezione vediamo come funziona la sintassi dell'ereditarietà in C#: impariamo a dichiarare classi derivate, capiamo cos'è la parola chiave base e come usarla per accedere alla classe genitore. Questo non solo ti semplificherà la vita quando scrivi codice, ma renderà anche il programma molto più elegante (e a volte ti farà dormire di più — perché non dovrai correggere errori di copia-incolla).
Ecco un esempio semplice dalla vita reale: abbiamo una classe Vehicle (mezzo di trasporto) — una specie di blueprint universale. Tutti i mezzi di trasporto possono andare, hanno un colore, un produttore, ecc.:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public void Drive()
{
Console.WriteLine("Andiamo!");
}
}
E ora vogliamo aggiungere un nuovo tipo di mezzo — l'automobile. L'auto è ancora un mezzo di trasporto, ma ha delle particolarità (tipo il numero di porte). Sarebbe strano riscrivere tutto su colore e produttore, visto che è già definito!
L'ereditarietà ci permette di dire: "Un'auto è un mezzo di trasporto, solo con un sacco di extra."
2. Come dichiarare una classe figlia
In C# per creare una classe derivata si usa i due punti :. Dopo il nome della classe indichiamo da quale classe ereditiamo.
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
}
Leggila così: la classe Car eredita tutti i campi e i metodi della classe Vehicle e aggiunge le sue cose — tipo il numero di porte.
Nota bene: in C# c'è solo ereditarietà singola delle classi (cioè una classe può ereditare solo UNA classe genitore). Ma per le interfacce — quante ne vuoi! (ne parleremo più avanti).
Come funziona?
Quando dichiari:
Car myCar = new Car();
...il tuo oggetto myCar ha accesso sia a tutti i campi/metodi di Vehicle, sia alle sue proprietà. Esempio:
myCar.Brand = "Toyota";
myCar.Color = "Blu";
myCar.NumberOfDoors = 4;
myCar.Drive(); // Metodo ereditato
Scenario reale:
Nel nostro "app in crescita" potresti fare un menu dei mezzi di trasporto, dove l'utente sceglie il tipo di mezzo e il programma mostra tutte le sue proprietà. Quando aggiungi un nuovo tipo di mezzo, scriverai pochissimo codice!
3. Ereditarietà dei costruttori
Quando creiamo oggetti della classe derivata, a volte vogliamo inizializzare le proprietà della classe base tramite il costruttore. Per esempio, per non dimenticare mai di indicare marca e colore:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
}
Ora nel costruttore della classe derivata puoi (e devi) chiamare il costruttore della classe base! Si usa la parola chiave base:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color) // chiamiamo il costruttore di Vehicle!
{
NumberOfDoors = numberOfDoors;
}
}
Come si vede nella pratica?
Car bmw = new Car("BMW", "Nero", 4);
Console.WriteLine($"{bmw.Brand}, {bmw.Color}, porte: {bmw.NumberOfDoors}");
bmw.Drive(); // Funziona ancora!
Quindi bmw — è sia un'auto che un mezzo di trasporto allo stesso tempo. Si ricorda dei suoi "genitori".
4. Differenza tra base e this
In C# ci sono due parole chiave per lavorare coi membri della classe: this e base.
- this — si riferisce all'oggetto corrente (tipo il suo campo o metodo).
- base — si riferisce a un membro della classe base (cioè al genitore).
Quando usare base? Immagina che vuoi estendere la logica di un metodo della classe base, o solo inizializzare la classe base dalla derivata. Per esempio:
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int gears)
: base(brand, color) // chiamiamo il costruttore della classe base
{
NumberOfGears = gears;
}
public void ShowInfo()
{
// Usiamo le proprietà della classe base!
Console.WriteLine($"{Brand} ({Color}), marce: {NumberOfGears}");
}
}
5. Parola chiave base nei metodi
base — non è solo per i costruttori! A volte vuoi cambiare il comportamento di un metodo della classe base, ma usare comunque parte della sua logica.
Supponiamo che vogliamo che il metodo Drive nella classe Car stampi anche che auto stiamo guidando:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void DriveCar()
{
base.Drive(); // chiamiamo l'implementazione base
Console.WriteLine($"Guidiamo {Brand} con {NumberOfDoors} porte!");
}
}
Spiegazione:
La chiamata base.Drive(); — è come dire: "Papà, prima racconta tu la tua parte della storia, poi aggiungo io i dettagli!". Così quando chiami DriveCar() verrà stampato:
Andiamo!
Guidiamo BMW con 4 porte!
Parleremo di override dei metodi (override) nelle prossime lezioni :P
6. Schema dell'ereditarietà
Visualizziamo le relazioni tra le classi. Ecco uno schema semplice (perché UML — fa paura, soprattutto la mattina!):
Vehicle (base)
/ \
Car Bicycle
- Tutte le frecce vanno DALLA derivata ALLA base.
- Tutte le classi figlie hanno accesso ai membri public/protected della classe base.
Differenze tra public, protected e private
Un piccolo promemoria per non fare confusione.
Nella classe derivata sono visibili:
| Modificatore | Visibilità nella derivata |
|---|---|
| public | Sì |
| protected | Sì |
| private | No |
Errore comune:
Provare ad accedere a un campo private dalla derivata:
public class Vehicle
{
private string secret = "Nessuno lo saprà!";
}
public class Car : Vehicle
{
public void RevealSecret()
{
Console.WriteLine(secret); // Errore! Non visibile.
}
}
Confronto: base vs. classe derivata
| Vehicle | Car (derivata) | |
|---|---|---|
| Brand | c'è | eredita |
| Color | c'è | eredita |
| Drive() | c'è | eredita (o estende) |
| NumberOfDoors | no | propria (proprietà unica) |
7. Esempio
Se continuiamo a sviluppare una semplice app "Gestione mezzi", potremmo strutturarla così:
public class Vehicle
{
public string Brand { get; set; }
public string Color { get; set; }
public Vehicle(string brand, string color)
{
Brand = brand;
Color = color;
}
public void Drive()
{
Console.WriteLine($"{Brand} {Color} è partito!");
}
}
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public Car(string brand, string color, int numberOfDoors)
: base(brand, color)
{
NumberOfDoors = numberOfDoors;
}
public void ShowCar()
{
Console.WriteLine($"Marca: {Brand}, Colore: {Color}, Porte: {NumberOfDoors}");
}
}
public class Bicycle : Vehicle
{
public int NumberOfGears { get; set; }
public Bicycle(string brand, string color, int numberOfGears)
: base(brand, color)
{
NumberOfGears = numberOfGears;
}
}
Nel metodo principale puoi creare una lista di mezzi e descriverli in modi diversi:
Car ford = new Car("Ford", "Rosso", 4);
ford.ShowCar();
ford.Drive();
Bicycle trek = new Bicycle("Trek", "Verde", 21);
trek.Drive();
8. Errori, particolarità e un po' di umorismo
Errore super comune — dimenticare di chiamare il costruttore base nella derivata. Se Vehicle non ha un costruttore senza parametri e non hai chiamato base(...), avrai un errore di compilazione: il compilatore si lamenterà che non può inizializzare la base. È come provare a costruire il secondo piano senza il primo. Non si può. Devi passare i dati in alto.
Secondo errore: dimenticare che i campi/metodi della classe base dichiarati private NON sono visibili nelle derivate. Se vuoi lasciare qualcosa ai "figli", usa protected (ne parliamo meglio nelle prossime lezioni).
E soprattutto: l'ereditarietà è comoda, ma non esagerare con gerarchie troppo complicate. Nella vita reale, se la tua catena di ereditarietà ha più di due-tre livelli, probabilmente qualcosa è andato storto. È facilissimo perdersi su chi eredita da chi e perché tutto questo esiste.
GO TO FULL VERSION