Classi ed Oggetti
Introduzione alle Classi in C#
- Da
structaclass: perché? - Dati + metodi insieme
- Incapsulamento e accesso controllato
- Costruttori e ciclo di vita
Parte I — Motivazione
Il problema con i dati "nudi"
Immaginiamo di rappresentare un orario:
// Approccio "naive": variabili separate int ora = 14; int minuto = 30; int secondo = 0; // Nessun controllo: questo è "valido"? ora = 99; minuto = -5;
Problema: chiunque può assegnare valori non validi. Non c'è un controllo dei valori e delle operazioni permesse sui dati.
Limitazioni dei dati separati
- Nessuna inizializzazione garantita — i valori di partenza non sono definiti
- Nessun controllo di validità — ora=99 è accettata dal compilatore
- Non è un'unità atomica — non posso passare "un orario" come singolo argomento
- Duplicazione del codice — ogni funzione deve rivalidare i dati
La soluzione: la Classe
Una classe raggruppa dati (campi) e comportamenti (metodi) in un'unica entità:
class NomeClasse { // 1. Campi (fields) — lo stato private tipo _nomeCampo; // 2. Costruttore — inizializzazione public NomeClasse(parametri) { // inizializza i campi } // 3. Metodi — il comportamento public tipoRitorno NomeMetodo(parametri) { // corpo del metodo } }
Modificatori di accesso
| Modificatore | Visibilità |
|---|---|
public |
Chiunque può accedere |
private |
Solo codice interno alla classe |
protected |
Classe + classi derivate |
Regola pratica: campi → private, metodi/costruttori → public
Convenzioni sui nomi
https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names
Punti principali che ci interessano:
uso di PascalCase per nomi di classi e nomi di metodi (iniziale maiuscola e iniziali delle parole che compongono il nome anch'esse maiuscole)
esempio: AutoSportiva
- campi privati devono iniziare con un underscore '_'
La classe Orario — prima versione
class Orario { private int _ora; private int _minuto; private int _secondo; public void ImpostaOrario(int h, int m, int s) { _ora = (h >= 0 && h < 24) ? h : 0; _minuto = (m >= 0 && m < 60) ? m : 0; _secondo = (s >= 0 && s < 60) ? s : 0; } public void StampaOrario() { Console.WriteLine($"{_ora:D2}:{_minuto:D2}:{_secondo:D2}"); } }
Usare la classe
// Creare un oggetto con "new" Orario pranzo = new Orario(); // Chiamare i metodi pranzo.ImpostaOrario(13, 0, 0); pranzo.StampaOrario(); // → 13:00:00 // Tentativo di accesso diretto ai campi: ERRORE di compilazione // pranzo._ora = 99; // 'Orario._ora' non e' accessibile
new alloca l'oggetto nella memoria heap e restituisce un riferimento ad esso.
Parte II — Costruttori
Cos'è un costruttore
- Metodo speciale che viene chiamato automaticamente con
new - Stesso nome della classe, nessun tipo di ritorno
- Garantisce che l'oggetto sia sempre in uno stato valido
- Se non ne scrivi uno, C# ne genera uno vuoto automaticamente
Costruttore con parametri
class Orario { private int _ora; private int _minuto; private int _secondo; // Costruttore public Orario(int ora, int minuto, int secondo) { _ora = (ora >= 0 && ora < 24) ? ora : 0; _minuto = (minuto >= 0 && minuto < 60) ? minuto : 0; _secondo = (secondo >= 0 && secondo < 60) ? secondo : 0; } public void Stampa() { Console.WriteLine($"{_ora:D2}:{_minuto:D2}:{_secondo:D2}"); } }
Costruttori multipli (overloading)
class Orario { private int _ora, _minuto, _secondo; // Costruttore completo public Orario(int ora, int minuto, int secondo) { ImpostaOrario(ora, minuto, secondo); } // Costruttore con soli ore e minuti public Orario(int ora, int minuto) : this(ora, minuto, 0) { } // chiama il costruttore sopra // Costruttore predefinito (mezzanotte) public Orario() : this(0, 0, 0) { } private void ImpostaOrario(int h, int m, int s) { if (h >= 0 && h < 24) { _ora = h; } else { ora = 0; } if ( m >= 0 && m < 60) { _minuto = m; } else { _minuto = 0; } if (s >= 0 && s < 60) { _secondo = s; } else { _secondo = 0; } } }
Usare più costruttori
Orario mezzanotte = new Orario(); // 00:00:00 Orario lezione = new Orario(8, 15); // 08:15:00 Orario fine = new Orario(10, 0, 0); // 10:00:00
In C# non esiste il distruttore nel senso di C++. Il Garbage Collector libera automaticamente la memoria quando l'oggetto non è più raggiungibile.
Parte IV — Proprietà
Le Properties di C#
C# introduce le properties: sintassi più pulita per i metodi che permettono di ottenere il valore di un attributo privato (getter) e per i metodi che permettono di impostare un valore per l'attributo (setter) https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties
class Orario { private int _ora; // attributo privato ( public int Ora { get { return _ora; } set { if (value >= 0 && value < 24) _ora = value; } } } // Utilizzo: sembra un campo, ma esegue il codice get/set Orario t = new Orario(); t.Ora = 14; // chiama il setter Console.WriteLine(t.Ora); // chiama il getter
Auto-properties
Quando non serve logica di validazione, si usa la forma breve:
class Studente { // Auto-property: il campo privato è generato automaticamente public string Nome { get; set; } public string Cognome { get; set; } public int Eta { get; set; } public Studente(string nome, string cognome, int eta) { Nome = nome; Cognome = cognome; Eta = eta; } public override string ToString() { return $"{Cognome} {Nome}, {Eta} anni"; } }
Proprietà in sola lettura
class Cerchio { public double Raggio { get; } // solo get → immutabile dopo costruzione public Cerchio(double raggio) { Raggio = raggio; } // Proprietà calcolata (non memorizzata) public double Area { get { return Math.PI * Raggio * Raggio; } } } Cerchio c = new Cerchio(5.0); Console.WriteLine(c.Area); // 78.53... // c.Raggio = 10; // ERRORE: proprietà in sola lettura
Parte V — Esempio Completo
La classe Conto Bancario
class ContoBancario { private decimal _saldo; public string Intestatario { get; } public string IBAN { get; } public decimal Saldo { get { return _saldo; } } public ContoBancario(string intestatario, string iban, decimal saldoIniziale = 0) { Intestatario = intestatario; IBAN = iban; _saldo = saldoIniziale >= 0 ? saldoIniziale : 0; }
La classe Conto Bancario (metodi)
public bool Deposita(decimal importo) { if (importo <= 0) return false; _saldo += importo; return true; } public bool Preleva(decimal importo) { if (importo <= 0 || importo > _saldo) return false; _saldo -= importo; return true; } public override string ToString() { return $"{Intestatario} | {IBAN} | Saldo: {_saldo}"; } }
Utilizzo della classe
Questo codice può essere inserito come top-level statement PRIMA della dichiarazione della classe, oppure racchiuso in una funzione Main()
Esempio come top-level statement
ContoBancario conto = new ContoBancario("Mario Rossi", "IT60X0542811101000000123456", 500); conto.Deposita(200); Console.WriteLine(conto); // Mario Rossi | ... | Saldo: €700,00 bool ok = conto.Preleva(1000); Console.WriteLine(ok); Console.WriteLine(conto.Saldo); // conto._saldo = 99999; // ERRORE: campo privato
Esempio con Main()
class ContoBancario { // resto della implementazione public static void Main() { ContoBancario conto = new ContoBancario("Mario Rossi", "IT60X0542811101000000123456", 500); conto.Deposita(200); Console.WriteLine(conto); // Mario Rossi | ... | Saldo: €700,00 bool ok = conto.Preleva(1000); Console.WriteLine(ok); Console.WriteLine(conto.Saldo); // conto._saldo = 99999; // ERRORE: campo privato } }
Parte VI — Riepilogo
Concetti chiave
| Concetto | C# |
|---|---|
| Definizione classe | class NomeClasse { ... } |
| Creazione oggetto | NomeClasse obj = new NomeClasse(); |
| Campo privato | private tipo _nomeCampo; |
| Costruttore | public NomeClasse(params) { ... } |
| Proprietà | public tipo Nome { get; set; } |
| Metodo | public tipoRet NomeMetodo() { ... } |
| Memoria | Gestita dal Garbage Collector |
Prossimi passi
- Ereditarietà: una classe può estendere un'altra (
class B : A) - Polimorfismo: metodi
virtualeoverride - Interfacce: contratti che una classe si impegna a rispettare
- Liste generiche:
Dictionary<K,V>
Fine
"Make it work, make it right, make it fast." — Kent Beck