1. Introduzione
I modificatori di accesso sono come recinzioni e serrature a casa tua: decidono chi può entrare in una stanza e chi può solo sbirciare dal buco della serratura. Ricordati, in C# sono questi:
| Modificatore | Chi vede |
|---|---|
|
Tutti |
|
Solo dentro la classe attuale |
|
Classe attuale e i figli (ereditarietà) |
|
Tutto il progetto (assembly) |
|
Tutto il progetto + i figli |
|
Solo i figli nello stesso progetto |
Il compito dell'incapsulamento è nascondere i dettagli dell'implementazione della classe, così nessuno può rovinare il tuo oggetto con una brutta riga tipo obj.Field = 99999;, se non vuoi che succeda.
2. Errori classici con i livelli di accesso
Aprire tutto con public
L'errore più comune è dichiarare tutti i campi e i metodi della classe come public. Il ragionamento è tipo: "Magari serve? Così gli altri possono usare i miei membri dove vogliono!". Sembra comodo, finché qualcuno non mette il tuo oggetto in uno stato assurdo, o inizia a usare i tuoi dettagli interni e poi cambiare il codice fa paura.
Esempio di errore:
public class BankAccount
{
public string Owner;
public decimal Balance;
}
Ora chiunque può fare così:
var acc = new BankAccount();
acc.Owner = "";
acc.Balance = -1000000M; // Improvvisamente, un debito da un milione!
Cosa c'è che non va?
Hai rotto tutte le regole di sicurezza e buon senso. Anche se la tua banca contiene solo foglie o soldi finti di "Monopoli", un saldo negativo "a piacere" è strano.
Trasformare la classe in una "structure senza struttura"
Il secondo errore tipico è rendere pubblici non solo i campi, ma anche proprietà e metodi interni che non dovrebbero essere accessibili da fuori.
public class Vault
{
public string DoorPIN = "1234";
public void OpenDoor()
{
// Un po' di magia
}
}
E adesso? Succede che chiunque può sapere il PIN e aprire la porta del tuo caveau. Anche se è un "caveau di test", tra un mese qualcuno si dimentica di questo buco e succede un casino.
Approccio giusto:
L'interfaccia della classe deve essere minima e protetta. Quello che serve agli altri — public. Quello che serve solo a te — private. Se serve ai figli — protected.
3. Errori di incapsulamento
In .NET c'è una regola chiara: nessun dato della classe (stato) deve stare in campi pubblici! Tutto deve essere chiuso o almeno incapsulato in proprietà. Accesso diretto ai campi = stile pessimo e fonte di bug difficili da trovare.
Perché i campi devono essere private
I campi sono la base dello stato interno dell'oggetto. Se li lasci pubblici, perdi il controllo su quando e che valori ci finiscono. Il tuo oggetto diventa vulnerabile a valori sbagliati, e se cambi il formato, tutte le chiamate pubbliche si rompono.
Meglio così:
Dichiara i campi come private e mostra fuori solo le proprietà o i metodi necessari:
public class BankAccount
{
private decimal balance;
public decimal Balance
{
get { return balance; }
private set
{
if (value < 0)
throw new ArgumentException("Il saldo non può essere negativo");
balance = value;
}
}
public BankAccount(decimal initialBalance)
{
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("Non puoi depositare zero o meno!");
Balance += amount;
}
}
Errore grave: public set per dati che non vanno cambiati
A volte serve solo il get, ma i programmatori scrivono public { get; set; } "di default", perché è più veloce. Così chiunque può fare:
account.Balance = 99999999; // Perché no?
Meglio così:
Se la proprietà va settata solo dentro la classe, metti il set con modificatore:
public decimal Balance { get; private set; }
oppure, se il valore va dato solo alla creazione dell'oggetto (C# 14):
public decimal Balance { get; init; }
4. Quando anche protected è una trappola
Porte spalancate per tutti i figli
protected è un buon modo per passare funzionalità ai figli, ma certi dati è meglio non renderli accessibili nemmeno agli eredi! Per esempio, se i tuoi figli (classi derivate) non devono toccare campi critici direttamente.
Esempio:
public class SecureVault
{
protected string secretCode = "1234";
}
Ora ogni figlio può fare così:
public class HackerVault : SecureVault
{
public void Hack()
{
secretCode = "0000"; // Cambiato il segreto in un attimo!
}
}
Consiglio:
Usa protected solo per quello che davvero serve ai figli. Tutto il resto lascialo privato, e per le operazioni necessarie scrivi metodi protetti.
5. Errori con internal e assembly confusi
Il modificatore internal sembra comodo: "così tutto è accessibile nel progetto". Ma appena il progetto cresce o usi librerie, arrivano i casini. Spesso gli studenti rendono qualcosa pubblico nell'assembly, anche se la classe doveva restare nascosta.
Esempio:
internal class Logger
{
internal void Write(string msg) { /* ... */ }
}
Ora chiunque può chiamare Logger.Write da qualsiasi file del progetto. E se tra un anno qualcuno collega la tua DLL a un altro progetto? Tutta la "cucina interna" sarà visibile, se hai lasciato qualcosa public.
Consiglio:
Usa internal per le classi tecniche, ma lascia i dettagli sensibili comunque private.
6. Pratica: app bancaria
Continuiamo il nostro esempio con l'app bancaria, così vedi nella pratica quanto è facile sbagliare e come rimediare.
Esempio di errore n°1: Campi public
public class BankAccount
{
public decimal Balance;
public string Owner;
}
Problema: chiunque può romperlo.
Correggiamo con le proprietà
public class BankAccount
{
private decimal _balance;
private string _owner;
public string Owner => _owner; // Sola lettura
public decimal Balance
{
get => _balance;
private set
{
if (value < 0) throw new ArgumentException("Il saldo non può essere negativo");
_balance = value;
}
}
public BankAccount(string owner, decimal initialBalance)
{
if (string.IsNullOrWhiteSpace(owner))
throw new ArgumentException("Il nome del proprietario è obbligatorio");
_owner = owner;
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("L'importo del deposito deve essere positivo");
Balance += amount;
}
}
Ora se provi così:
var acc = new BankAccount("Lena", 1000m);
acc.Balance = -700m; // Errore! set è privato.
O anche così:
acc.Owner = "Hacker"; // Errore di compilazione, niente set
— non funziona. Solo tramite costruttore o metodi.
Errore: Proprietà "per bellezza"
Molti scrivono:
public int Age { get; set; }
Anche se l'età dovrebbe cambiare solo con il metodo IncreaseAge (tipo il 1 gennaio), e non direttamente.
7. Promemoria visivo: come non si fa e come si fa
graph TD
A[Campi Public] -->|Qualsiasi codice| D[Stato fuori controllo]
B[Campi Private + Metodi Public] -->|Ben definito| E[Stato controllato]
F[Setters Public] -->|Chiunque può cambiare| D
G[Setters Private] -->|Solo la classe| E
| Approccio | Stato protetto? | Si può validare? | Facile da estendere? |
|---|---|---|---|
| Campi public | ❌ | ❌ | ❌ |
| Proprietà (get/set public) | ❌ | Parzialmente | Sì, ma non sicuro |
| Campi private + proprietà con set private | ✅ | ✅ | ✅ |
8. Consigli di stile per l'incapsulamento
- Esporre solo quello che davvero serve all'utente della classe.
- Valida sempre i dati che entrano nell'oggetto.
- Non esagerare: se una proprietà non va cambiata da fuori — metti il set privato.
- Per logica particolare — usa sempre i metodi, non l'accesso diretto.
- Anche se ti tenta — non fare campi public "per test".
- Usa le proprietà invece dei campi anche per la sola lettura pubblica — così puoi modificarle facilmente dopo.
GO TO FULL VERSION