1. Introduzione
Immagina questa situazione. Stai salvando dati sui gatti nel codice. E vuoi, oltre ad esempio all'età del gatto, salvare anche stati tipo "nessun dato", "età sconosciuta". Potresti salvare 0, ma zero è un'età valida.
int age = 0; // Età del gatto. Ma cosa vuol dire 0? È un gattino o "sconosciuto"?
Il problema è che int accetta solo valori interi, e "nessun dato" tra i numeri interi non esiste. Se l'età fosse una stringa, potresti assegnare null. Ma con i numeri non funziona:
int age = null; // Errore! Non puoi assegnare null a una variabile di tipo int
Questo succede perché i tipi valore (struct come int, double, DateTime, bool) hanno sempre un valore. Non hanno uno stato "vuoto" (a differenza dei tipi reference, dove null è semplicemente l'assenza di un oggetto).
Esempio dalla vita reale:
Per aggirare questa limitazione, a volte i programmatori inventano valori "speciali": tipo -1 o int.MaxValue per "nessun valore". Ma è brutto, scomodo e rischioso: puoi confondere un valore reale con un segnaposto.
2. Tipo Nullable: un tipo che può essere "null"
Idea
E se permettessimo ai numeri normali (e a tutti i tipi valore) di contenere non solo un valore, ma anche null?
In C# questo si fa con una classe wrapper speciale: Nullable<T>. Questa classe può contenere o un valore di tipo T, oppure niente (cioè null).
Sintassi
int? age = null; // La variabile age può essere sia un numero che null
Il compilatore C#, quando vede questo codice, in realtà lo interpreta come:
Nullable<int> age = new Nullable<int>(); // La variabile age non contiene un valore
Oppure, se vuoi specificare un valore:
Nullable<int> age = new Nullable<int>(42); // age contiene il numero 42
Quindi, il punto di domanda trasforma qualsiasi tipo valore nella sua versione nullable:
- int? (uguale a Nullable<int>)
- double?
- DateTime?
- e qualsiasi altra struct
Wrapper Nullable per le struct più usate
| Tipo normale | Tipo Nullable | Esempio |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Puoi anche scrivere in modo più lungo, usando la classe wrapper:
Nullable<int> pages = null;
Ma scrivere int? è più corto, quindi più figo.
3. Come usare un tipo nullable
Assegnazione e controllo
Assegnare null funziona come per i tipi reference:
Dichiarazione e assegnazione
int? temperature = null;
temperature = 25;
Controllo su null
Come capire se il nostro tipo nullable ha davvero un valore?
if (temperature != null)
{
Console.WriteLine($"Temperatura: {temperature}");
}
else
{
Console.WriteLine("Nessun dato sulla temperatura");
}
Proprietà dei tipi Nullable: .HasValue e .Value
I tipi nullable hanno due proprietà importanti:
- .HasValue — restituisce true se la variabile contiene qualcosa (non null).
- .Value — il valore vero e proprio, se c'è (altrimenti lancia un'eccezione).
int? temperature = null;
if (temperature.HasValue) //nessuna eccezione!
{
Console.WriteLine($"Temperatura: {temperature.Value}°C");
}
else
{
Console.WriteLine("Temperatura sconosciuta");
}
Questo codice funziona: temperature non è null, contiene null dentro. E puoi sempre chiamare la proprietà HasValue su un tipo Nullable. Ma provare a prendere .Value quando non c'è — brutta idea, ti becchi un'eccezione InvalidOperationException. Controlla sempre prima se c'è un valore!
Sintassi abbreviata per la stampa
Spesso basta usare la variabile nullable — e C# capisce tutto da solo:
int? hours = null;
Console.WriteLine(hours); // non stampa niente (solo vuoto)
hours = 10;
Console.WriteLine(hours); // stampa 10
4. Operazioni e tipi nullable: aritmetica, confronti, conversioni
Operazioni: cosa succede?
Se uno degli operandi è nullable, anche il risultato è nullable.
E se uno degli operandi è null, anche il risultato è null.
int? a = 5;
int? b = null;
int? sum = a + b; // sum == null
int? c = 10;
int? d = 15;
int? total = c + d; // total == 25
Confronti
Puoi confrontare tipi nullable con numeri normali:
int? score = null;
if (score > 0) Console.WriteLine("Ottimo risultato!");
else Console.WriteLine("Nessun risultato"); // Va qui
Se score è null, la condizione score > 0 sarà false.
Conversione esplicita e implicita
- Da tipo normale a nullable: automatico.
- Da nullable a normale: solo se non è null (altrimenti eccezione).
int? x = 3;
int y = x.Value; // Ok, se x non è null
// Implicito
int? z = y;
5. Nullable e metodi: parametri e valori di ritorno
Restituire valori nullable
Se un metodo non può sempre restituire un risultato, usa tipi nullable come valore di ritorno:
// Il metodo cerca un utente per login e restituisce la sua età se trovato, altrimenti null
int? FindUserAge(string login)
{
// ... qui la logica di ricerca
return null; // se non trovato
}
Parametri nullable
Puoi accettare valori nullable nei parametri, così è chiaro: “può esserci, ma anche no”.
void PrintTemperature(int? temp)
{
if (temp == null)
Console.WriteLine("Temperatura sconosciuta");
else
Console.WriteLine($"Temperatura: {temp} gradi");
}
GO TO FULL VERSION