Costruttori e Distruttori in C#

I costruttori e distruttori (finalizzatori) in C# sono metodi speciali che gestiscono rispettivamente l’inizializzazione e la pulizia degli oggetti. I costruttori preparano un oggetto per l’uso, mentre i distruttori (finalizzatori) si occupano della pulizia delle risorse prima che l’oggetto venga rimosso dalla memoria dal Garbage Collector.
Costruttori
Costruttore Predefinito
public class Persona
{
public string Nome { get; set; }
public int EtĂ { get; set; }
// Costruttore predefinito (senza parametri)
public Persona()
{
Nome = "Sconosciuto";
EtĂ = 0;
Console.WriteLine("Costruttore predefinito chiamato");
}
}
// Utilizzo
var persona = new Persona(); // Nome = "Sconosciuto", EtĂ = 0
Costruttori Parametrizzati
public class Prodotto
{
public string Nome { get; set; }
public decimal Prezzo { get; set; }
public DateTime DataCreazione { get; set; }
// Costruttore con parametri
public Prodotto(string nome, decimal prezzo)
{
Nome = nome ?? throw new ArgumentNullException(nameof(nome));
Prezzo = prezzo >= 0 ? prezzo : throw new ArgumentException("Il prezzo non può essere negativo");
DataCreazione = DateTime.Now;
}
// Overload del costruttore
public Prodotto(string nome) : this(nome, 0m)
{
// Chiama il costruttore principale con prezzo 0
}
}
Concatenazione di Costruttori (this)
public class BankAccount
{
public string Numero { get; private set; }
public decimal Saldo { get; private set; }
public string Proprietario { get; private set; }
// Costruttore principale
public BankAccount(string numero, decimal saldoIniziale, string proprietario)
{
Numero = numero ?? throw new ArgumentNullException(nameof(numero));
Saldo = saldoIniziale;
Proprietario = proprietario ?? throw new ArgumentNullException(nameof(proprietario));
// Logica di inizializzazione complessa
ValidaNumeroAccount();
RegistraCreazione();
}
// Costruttore che delega al principale
public BankAccount(string numero, string proprietario)
: this(numero, 0m, proprietario)
{
// Saldo iniziale di default a 0
}
// Costruttore con solo proprietario
public BankAccount(string proprietario)
: this(GeneraNumeroAccount(), 0m, proprietario)
{
// Numero account generato automaticamente
}
private static string GeneraNumeroAccount() => Guid.NewGuid().ToString("N")[..10];
private void ValidaNumeroAccount() { /* validazione */ }
private void RegistraCreazione() { /* logging */ }
}
Costruttori Statici
public class ConfigurazioneApp
{
public static string ConnectionString { get; private set; }
public static int Timeout { get; private set; }
// Costruttore statico - chiamato una sola volta
static ConfigurazioneApp()
{
Console.WriteLine("Inizializzazione configurazione...");
ConnectionString = Environment.GetEnvironmentVariable("DB_CONNECTION")
?? "Server=localhost;Database=MyDB";
Timeout = int.Parse(Environment.GetEnvironmentVariable("TIMEOUT") ?? "30");
// Inizializzazione pesante che avviene solo una volta
CaricaConfigurazioniAvanzate();
}
private static void CaricaConfigurazioniAvanzate()
{
// Logica di inizializzazione costosa
}
}
Inizializzatori di Oggetti e Costruttori
public class Utente
{
public string Nome { get; set; }
public string Email { get; set; }
public DateTime DataRegistrazione { get; set; }
public List<string> Ruoli { get; set; }
public Utente()
{
DataRegistrazione = DateTime.Now;
Ruoli = new List<string>();
}
public Utente(string nome, string email) : this()
{
Nome = nome;
Email = email;
}
}
// Utilizzo con inizializzatore
var utente = new Utente
{
Nome = "Mario",
Email = "mario@example.com",
Ruoli = { "User", "Admin" } // Chiama Add sulla lista inizializzata nel costruttore
};
Distruttori (Finalizzatori)
Finalizzatore Base
public class GestoreFile
{
private FileStream? fileStream;
private bool disposed = false;
public GestoreFile(string percorso)
{
fileStream = new FileStream(percorso, FileMode.OpenOrCreate);
}
// Finalizzatore (distruttore)
~GestoreFile()
{
Console.WriteLine("Finalizzatore chiamato");
Dispose(false); // Cleanup solo di risorse non gestite
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Cleanup di risorse gestite
fileStream?.Dispose();
}
// Cleanup di risorse non gestite (se presenti)
// IntPtr, handle di sistema, ecc.
disposed = true;
}
}
}
Pattern IDisposable Completo
public class RisorsaComplessa : IDisposable
{
private FileStream? managedResource;
private IntPtr unmanagedResource; // Esempio di risorsa non gestita
private bool disposed = false;
public RisorsaComplessa(string file)
{
managedResource = new FileStream(file, FileMode.Create);
unmanagedResource = AcquireUnmanagedResource();
}
// Implementazione IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Previene chiamata del finalizzatore
}
// Pattern Dispose protetto
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Cleanup risorse gestite
managedResource?.Dispose();
managedResource = null;
}
// Cleanup risorse non gestite
if (unmanagedResource != IntPtr.Zero)
{
ReleaseUnmanagedResource(unmanagedResource);
unmanagedResource = IntPtr.Zero;
}
disposed = true;
}
}
// Finalizzatore
~RisorsaComplessa()
{
Dispose(false);
}
// Metodi helper per risorse non gestite
private static IntPtr AcquireUnmanagedResource() => new IntPtr(1);
private static void ReleaseUnmanagedResource(IntPtr resource) { }
// Controllo dello stato
private void ThrowIfDisposed()
{
if (disposed)
throw new ObjectDisposedException(GetType().Name);
}
public void DoWork()
{
ThrowIfDisposed();
// Logica di lavoro
}
}
Costruttori in EreditarietĂ
public class Veicolo
{
public string Marca { get; protected set; }
public string Modello { get; protected set; }
public int Anno { get; protected set; }
protected Veicolo(string marca, string modello, int anno)
{
Marca = marca;
Modello = modello;
Anno = anno;
Console.WriteLine($"Costruttore Veicolo: {marca} {modello}");
}
}
public class Auto : Veicolo
{
public int NumeroPorte { get; private set; }
public Auto(string marca, string modello, int anno, int numeroPorte)
: base(marca, modello, anno) // Chiama costruttore della classe base
{
NumeroPorte = numeroPorte;
Console.WriteLine($"Costruttore Auto: {numeroPorte} porte");
}
}
// Utilizzo
var auto = new Auto("Toyota", "Corolla", 2023, 4);
// Output:
// Costruttore Veicolo: Toyota Corolla
// Costruttore Auto: 4 porte
Record e Costruttori Primari (C# 9.0+)
// Record con costruttore primario
public record Persona(string Nome, string Cognome, int EtĂ )
{
// Costruttore aggiuntivo
public Persona(string nomeCompleto) : this(
nomeCompleto.Split(' ')[0],
nomeCompleto.Split(' ')[1],
0)
{
}
// Validazione nel costruttore primario
public string Nome { get; init; } = !string.IsNullOrEmpty(Nome) ? Nome
: throw new ArgumentException("Nome non può essere vuoto");
}
// Classe con costruttore primario (C# 12.0+)
public class Prodotto(string nome, decimal prezzo)
{
public string Nome { get; } = nome ?? throw new ArgumentNullException(nameof(nome));
public decimal Prezzo { get; } = prezzo >= 0 ? prezzo
: throw new ArgumentException("Prezzo deve essere positivo");
public DateTime DataCreazione { get; } = DateTime.Now;
}
Best Practices
Validazione nei Costruttori
public class Email
{
public string Indirizzo { get; }
public Email(string indirizzo)
{
// Validazione immediata
if (string.IsNullOrWhiteSpace(indirizzo))
throw new ArgumentException("Indirizzo email richiesto", nameof(indirizzo));
if (!indirizzo.Contains("@"))
throw new ArgumentException("Formato email non valido", nameof(indirizzo));
Indirizzo = indirizzo.Trim().ToLowerInvariant();
}
// Factory method per validazione piĂą complessa
public static Email Create(string indirizzo)
{
// Validazione avanzata con regex, DNS check, ecc.
return new Email(indirizzo);
}
}
Gestione delle Eccezioni
public class DatabaseConnection
{
private readonly string connectionString;
private SqlConnection? connection;
public DatabaseConnection(string connectionString)
{
this.connectionString = connectionString ??
throw new ArgumentNullException(nameof(connectionString));
try
{
connection = new SqlConnection(connectionString);
connection.Open(); // Test di connessione
}
catch (SqlException ex)
{
throw new InvalidOperationException(
"Impossibile stabilire connessione al database", ex);
}
}
~DatabaseConnection()
{
connection?.Dispose();
}
}
Considerazioni sulle Performance
Evitare Lavoro Pesante nei Costruttori
// ❌ Evitare operazioni pesanti nel costruttore
public class HeavyService
{
public HeavyService()
{
// ❌ Operazioni pesanti bloccano la creazione dell'oggetto
LoadLargeDataset();
InitializeComplexAlgorithm();
}
}
// âś… Inizializzazione lazy o asincrona
public class OptimizedService
{
private bool initialized = false;
public OptimizedService()
{
// Solo inizializzazione leggera
}
public async Task InitializeAsync()
{
if (!initialized)
{
await LoadLargeDatasetAsync();
await InitializeComplexAlgorithmAsync();
initialized = true;
}
}
private Task LoadLargeDatasetAsync() => Task.CompletedTask;
private Task InitializeComplexAlgorithmAsync() => Task.CompletedTask;
}
Conclusione
Costruttori e distruttori sono elementi fondamentali della programmazione orientata agli oggetti in C#. I costruttori garantiscono che gli oggetti siano inizializzati correttamente, mentre i distruttori (finalizzatori) gestiscono la pulizia delle risorse. L’evoluzione del linguaggio ha introdotto costruttori primari e pattern più eleganti per la gestione delle risorse. La comprensione approfondita di questi meccanismi è essenziale per scrivere codice robusto, efficiente e privo di memory leak, specialmente quando si lavora con risorse non gestite o connessioni esterne.