Classi ed Oggetti

Introduzione alle Classi in C#

  • Da struct a class: 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 virtual e override
  • 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