1. Introduzione
Programmare in C# senza proprietà si può, ma è un po' come andare sui rollerblade dentro la Casa Bianca — si può, ma è scomodo. Ormai ci siamo abituati alla sintassi corta senza getter e setter inutili, ma quando la nostra app inizia a lavorare con modelli "seri" (tipo una classe User che descrive un utente nel sistema), diventa importante garantire che tutti i dati necessari ci siano davvero.
Per esempio, se abbiamo una classe così:
public class User
{
public string Nome { get; set; }
public int Eta { get; set; }
}
È facile dimenticarsi di inizializzare le proprietà:
User user = new User(); // Nome sarà null, Eta = 0
Risultato: da qualche parte dopo dieci schermate e cento giri di logica ti becchi il famoso NullReferenceException e ci metti una vita a capire di chi è la colpa.
Ricordiamoci delle proprietà init-only
Sì, possiamo usare l'inizializzazione solo alla creazione:
public string Nome { get; init; }
Ma anche con questa sintassi nessuno obbliga l'utente della classe a passare un valore se non vuole — la maggior parte dei costruttori di default inizializzano i campi con i valori di default (null, 0 ecc.).
Quindi come facciamo a obbligare il programmatore, o anche noi stessi, a non dimenticare di mettere il valore giusto? Proprio per questo è stato inventato il modificatore required, apparso in C# 11.
2. Proprietà required: inizializzazione rigorosa
Il modificatore required dice al compilatore di controllare che una certa proprietà dell'oggetto sia impostata esplicitamente al momento della creazione. In pratica, se una proprietà così non è inizializzata quando crei l'oggetto — il compilatore non ti lascia passare e l'IDE ti disegna una bella onda rossa.
public class User
{
public required string Nome { get; set; }
public int Eta { get; set; }
}
Proviamo a creare un utente:
// Errore di compilazione: la proprietà Nome è obbligatoria da inizializzare!
User user1 = new User();
Oppure così:
// Errore di compilazione: non è stata indicata la proprietà required Nome
User user2 = new User { Eta = 18 };
Ecco invece il modo giusto:
User user3 = new User { Nome = "Germiona", Eta = 18 };
Come funziona required?
Il modificatore required dice al compilatore: "Dopo che il costruttore dell'oggetto (qualsiasi!) è finito, questa proprietà deve essere impostata esplicitamente".
Funziona sia per le proprietà normali che per quelle init-only:
public required string Nome { get; init; }
Se definisci un costruttore personalizzato che inizializza il valore della proprietà required, allora va bene lo stesso.
Schema tipico di controllo
| Scenario | Compilatore contento? |
|---|---|
| Proprietà required non impostata | ❌ |
| Impostata nell'object initializer | ✔️ |
| Impostata nel costruttore | ✔️ |
Esempio nel nostro modello "Cane"
public class Cane
{
public required string Nome { get; set; }
public int Eta { get; set; }
}
Cane cane = new Cane { Nome = "Bobik", Eta = 5 }; // Tutto ok!
Cane caneBrutto = new Cane { Eta = 2 }; // Errore! Nome non specificato
Dove aiuta davvero required?
- Passaggio di DTO tra layer: Se hai un'API, serve che arrivino sempre tutti i campi necessari.
- Modelli complessi con attributi obbligatori: Tipo Product con SKU obbligatorio, Order con numero obbligatorio.
- Ai colloqui e code review: Se mostri questa sintassi, probabilmente il tuo codice verrà letto con rispetto (e un po' di invidia).
3. Come funziona required con i costruttori?
A volte scrivi tu il costruttore. Cosa succede se non inizializzi la proprietà required nel costruttore o nell'object initializer? Il compilatore ti dà errore.
public class Articolo
{
public required string Titolo { get; set; }
public required string Autore { get; set; }
public Articolo()
{
// Se non inizializzi Titolo e Autore — errore di compilazione!
// Puoi fare così:
Titolo = "Bez nazvaniya";
Autore = "Neizvestnyj";
}
}
Se il costruttore assegna i valori alle proprietà required — tutto ok. Se no, devi inizializzare questi campi tramite l'object initializer (new Articolo { ... }).
Specificità d'uso
- required funziona solo con le proprietà, non con i campi.
- required non è ereditato — se la proprietà base è required, ma nella derivata non metti required, il compilatore non si lamenta (ma è buona pratica ripetere required anche nella derivata).
- required non si può usare con i campi automatici o altro che non sia una property.
4. Proprietà con sintassi a freccia
Con le nuove versioni di C#, gli sviluppatori puntano sempre di più a codice compatto e chiaro. Una delle novità più evidenti per le proprietà è la sintassi a freccia (o expression-bodied properties).
A volte vuoi definire una proprietà che restituisce solo un valore senza logica extra. Prima dovevi scrivere il getter completo con le graffe:
public int Eta
{
get { return annoNascita > 0 ? DateTime.Now.Year - annoNascita : 0; }
}
Ora puoi scrivere molto più corto — usando la freccia (=>):
public int Eta => annoNascita > 0 ? DateTime.Now.Year - annoNascita : 0;
Questa sintassi si chiama proprietà con corpo-espressione (expression-bodied property). È perfetta per calcoli semplici e rende il codice più compatto.
Esempio di uso di get e set
public class Libro
{
private string _titolo;
public string Titolo
{
get => _titolo;
set => _titolo = value.Trim();
}
}
Qui get restituisce il valore del campo, e set — assegna, togliendo prima gli spazi ai lati.
Esempio di uso solo di get (sola lettura):
Se la proprietà è sola lettura — puoi scriverla senza graffe, solo con =>.
public class Persona
{
private string nome = "Mark Tven";
// Sola lettura: proprietà calcolata
public string Nome => nome.ToUpper();
}
Qui la proprietà Nome si può solo leggere — restituisce sempre nome in maiuscolo.
5. Parola chiave field per le proprietà
Prima di C# 14, se volevi accedere al campo nascosto di una proprietà automatica direttamente nel setter o getter (tipo per evitare ricorsione o aggiungere logica tua) — non si poteva. Dovevi dichiarare il campo a mano.
C# 14 ti permette di accedere al campo nascosto della proprietà automatica con la parola chiave field:
public class Persona
{
public string Nome
{
get => field; // field — è il campo nascosto della proprietà Nome
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Il nome non può essere vuoto!");
field = value; // usiamo field invece di _nome
}
}
}
Prima dovevi fare così:
private string _nome;
public string Nome
{
get => _nome;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Il nome non può essere vuoto!");
_nome = value;
}
}
Una dichiarazione di variabile in meno. Più il codice è compatto, meglio è.
Perché a volte è importante accedere proprio al campo dentro la proprietà?
- A volte vuoi controllare esplicitamente dove e come viene salvato il valore (tipo se vuoi restituire una copia dell'oggetto, fare caching o lazy-loading).
- Se vuoi usare attributi o reflection — il campo può servirti per nome.
- In certi casi di (de)serializzazione o performance-tuning vuoi più controllo su come viene salvato il valore.
Varianti d'uso:
Validazione dati nel setter
public double Voto
{
get => field;
set
{
if (value < 0 || value > 5)
throw new ArgumentOutOfRangeException("La valutazione deve essere da 0 a 5");
field = value;
}
}
Statistiche sui cambi di valore
public int ConteggioPassi
{
get => field;
set
{
if (value > field)
{
Console.WriteLine($"Evviva! Hai fatto {value - field} passi in più!");
}
field = value;
}
}
Lazy Load
public string Dati
{
get
{
if (field == null)
field = CaricaDatiDalDatabase();
return field;
}
set => field = value;
}
6. Errori tipici e sfumature
Errore n°1: hai dimenticato di inizializzare la proprietà required.
Il compilatore non ti lascia passare con questo codice e ti dà errore subito in compilazione, così eviti problemi a runtime.
Errore n°2: le proprietà required non funzionano senza inizializzazione completa nel costruttore.
Se il costruttore non prende i valori per tutte le proprietà obbligatorie, il compilatore ti ricorda che ti sei dimenticato qualcosa.
Errore n°3: tentativo di usare required con const o readonly.
Questi modificatori non sono compatibili — required si può usare solo con le proprietà normali. Se provi a combinarli, ottieni errore.
GO TO FULL VERSION