1. Il problema di NullReferenceException
Praticamente ogni programmatore alle prime armi (e non solo) in C# si è imbattuto almeno una volta in questo messaggio spaventoso:
System.NullReferenceException: Object reference not set to an instance of an object.
Ecco un classico esempio:
string hello = null;
Console.WriteLine(hello.Length); // BAM! NullReferenceException
Il senso è semplice: stai cercando di accedere a un oggetto che non esiste. Cioè la variabile punta a null — uno spazio vuoto invece di un oggetto.
Perché è facile commettere questo errore?
Perché la maggior parte dei tipi reference in C# storicamente potevano assumere il valore null. Gli sviluppatori spesso si dimenticavano di controllare che la variabile puntasse davvero a un oggetto, e di conseguenza beccavano la famosa NullReferenceException.
Se uno sviluppatore ricevesse un dollaro per ogni NullReferenceException, avrebbe già scritto il suo sistema operativo.
Perché è un problema?
Prima abbiamo imparato a fare in modo che i valori tipo int? o double? potessero essere null — per i casi in cui serve indicare esplicitamente "assenza di valore" (ad esempio, un campo nel database può essere vuoto).
Ma i tipi reference (tipo string, qualsiasi classe, array ecc.) potevano sempre essere null. È stato così fin dalla nascita di C#. Comodo, sì, ma anche pericoloso — perché il linguaggio non ci obbligava a pensare: "Questa reference può essere vuota?"
2. Evoluzione: Nullable Reference Types (NRT)
5 anni fa è arrivata una novità che ha rivoluzionato il modo di combattere le NullReferenceException — i Nullable Reference Types (NRT).
string s = "hello"; // not-nullable reference
string? maybe = null; // nullable reference
L'idea principale:
- Separare in modo rigoroso le variabili reference che non possono mai essere null da quelle che invece possono essere vuote.
- Aiutare lo sviluppatore a vedere i potenziali problemi con null già in fase di compilazione, non a runtime (quando ormai è troppo tardi).
Nelle nuove versioni di C# è cambiato l'approccio alla dichiarazione delle variabili reference: di default non puoi assegnare null, a meno che tu non lo permetta esplicitamente.
Basta un solo simbolo ? e il significato della variabile cambia subito!
3. Attivare i Nullable Reference Types
Di default questa sintassi rigorosa è disattivata nella maggior parte dei progetti, per non rompere il vecchio codice. Ma i template moderni di progetto in Visual Studio, Rider e .NET CLI già creano progetti con NRT attivi — o almeno ti consigliano caldamente di farlo.
Per sapere se gli NRT sono attivi nel tuo progetto, cerca nel file .csproj questa riga:
<Nullable>enable</Nullable>
Se questa riga non c'è, puoi aggiungerla a mano — è sicuro.
Come influisce sul codice?
- Se gli NRT sono disattivati (vecchio comportamento): string s = null; — nessun errore, tutto ok.
- Se sono attivi: il compilatore si lamenta se provi a mettere null dove non dovrebbe esserci null.
4. Esempi con NRT
Esempio semplice
#nullable enable // Questa riga attiva il controllo NRT per questo file
string notNullable = "Ciao";
string notNullable2 = null; // ERRORE di compilazione!
string? nullableString = null; // Tutto ok, abbiamo permesso esplicitamente null
Nella prima riga abbiamo dichiarato una stringa che deve sempre puntare a un oggetto reale.
Nella terza — una stringa che può essere null.
Controllo su null
void PrintLength(string? s)
{
// Il compilatore si lamenterà: "E se s == null?"
Console.WriteLine(s.Length);
// Così invece va bene
if (s != null)
{
Console.WriteLine(s.Length);
}
}
Il compilatore ora ti aiuta a non dimenticare i controlli!
Avvisi del compilatore
Se ignori l'avviso e provi comunque ad accedere a una variabile nullable senza controllo — riceverai un nuovo (e molto utile!) avviso dal compilatore. Non è un errore (il programma si compila lo stesso), ma la "lampadina" gialla ti suggerisce: "Sei sicuro, amico?"
Confrontiamo le modalità:
| Tipo C# | Può essere null? | Modalità Legacy (prima di NRT) | Modalità NRT (#nullable enable) |
|---|---|---|---|
| int | No | No | No |
| int? | Sì | Sì | Sì |
| string | Sì | No (sempre possibile) | No (di default non si può) |
| string? | Sì | No | Sì |
5. Consigli: come e perché usare gli NRT
- Meno bug: Meno crash improvvisi per colpa di null, più felicità per dev e utenti.
- Codice più chiaro: Si vede subito cosa può essere vuoto e cosa deve essere sempre valorizzato.
- Aiuto dal compilatore: Ammettilo — ci sta davvero aiutando! Gli avvisi NRT sono una fonte preziosa di info su possibili errori.
Dove è indispensabile
- Nei progetti grandi, dove tante persone lavorano sullo stesso codice.
- Negli API e nelle librerie pubbliche — per spiegare agli altri cosa possono e non possono fare.
- Dove serve affidabilità (tipo app bancarie, sistemi medici ecc.)
6. Errori tipici e trappole
- "Hai dimenticato il ?"
Assegni null a una stringa normale (string s = null;) — e il compilatore si lamenta, perché ora di default una stringa normale non può essere null. - "Hai esagerato con il ?"
Fai tutte le variabili string? solo per non far arrabbiare il compilatore. Ma il senso è annotare con attenzione dove davvero può esserci un valore vuoto. - "Hai frainteso l'avviso"
Ignori l'avviso e poi becchi una NullReferenceException dove pensavi che il compilatore ti avrebbe protetto.
GO TO FULL VERSION