Comprendere la nullabilità
Se si lavora come sviluppatore .NET, è probabile che ci si sia imbattuti in System.NullReferenceException. Questa eccezione si verifica in fase di esecuzione quando un null
viene dereferenziato, ovvero quando una variabile viene valutata in fase di esecuzione, ma la variabile fa riferimento a null
. Questa eccezione è di gran lunga l'eccezione che si verifica più di frequente all'interno dell'ecosistema .NET. Il creatore di null
, Sir Tony Hoare, si riferisce a null
come l'"errore da un miliardo di dollari".
Nell'esempio seguente la variabile FooBar
viene assegnata a null
e immediatamente dereferenziata, espondendo quindi il problema:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();
// The FooBar type definition.
record FooBar(int Id, string Name);
Il problema diventa molto più difficile da individuare come sviluppatore quando le app aumentano di dimensioni e complessità. Individuare potenziali errori come questo è un compito per gli strumenti, e il compilatore C# è qui per fornire aiuto.
Definizione della sicurezza Null
Il termine Null safety definisce un set di funzionalità specifiche per i tipi annullabili che consente di ridurre il numero di occorrenze NullReferenceException
possibili.
Considerando l'esempio FooBar
precedente, si potrebbe evitare l'eccezioneNullReferenceException
verificando se la variabile fooBar
era null
prima di dereferenziarla:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Check for null
if (fooBar is not null)
{
_ = fooBar.ToString();
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
Per facilitare l'identificazione di scenari come questo, il compilatore può dedurre la finalità del codice e applicare il comportamento desiderato. Tuttavia, ciò è possibile solo quando è abilitato un contesto annullabile. Prima di discutere del contesto nullable, descriviamo i possibili tipi nullable.
Tipi nullable
Prima di C# 2.0, solo i tipi di riferimento erano nullabili. Tipi di valore come int
o DateTime
non possono essere null
. Se questi tipi vengono inizializzati senza un valore, tornano al loro valore default
. Nel caso di un oggetto int
, si tratta di 0
. Per un DateTime
, è DateTime.MinValue
.
I tipi di riferimento di cui è stata creata un'istanza senza valori iniziali funzionano in modo diverso. Il valore default
per tutti i tipi di riferimento è null
.
Si consideri il frammento di codice C# seguente:
string first; // first is null
string second = string.Empty // second is not null, instead it's an empty string ""
int third; // third is 0 because int is a value type
DateTime date; // date is DateTime.MinValue
Nell'esempio precedente:
first
ènull
perché il tipo riferimentostring
è stato dichiarato ma non è stata effettuata alcuna assegnazione.second
viene assegnato astring.Empty
quando viene dichiarato. L'oggetto non ha mai avuto un'assegnazionenull
.third
è0
nonostante non sia stato assegnato. Si tratta di unstruct
(tipo valore) e ha un valoredefault
di0
.date
non è inizializzato, ma il relativo valoredefault
è System.DateTime.MinValue.
A partire da C# 2.0, è possibile definire i tipi valore nullable usando Nullable<T>
(o T?
per abbreviato). In questo modo, i tipi valore possono essere nullable. Si consideri il frammento di codice C# seguente:
int? first; // first is implicitly null (uninitialized)
int? second = null; // second is explicitly null
int? third = default; // third is null as the default value for Nullable<Int32> is null
int? fourth = new(); // fourth is 0, since new calls the nullable constructor
Nell'esempio precedente:
first
ènull
perché il tipo di valore nullable non è inizializzato.second
viene assegnato anull
quando viene dichiarato.third
ènull
come il valoredefault
perNullable<int>
ènull
.fourth
è0
come l'espressionenew()
chiama il costruttoreNullable<int>
eint
è0
per impostazione predefinita.
C# 8.0 ha introdotto tipi riferimento nullable, in cui è possibile esprimere la finalità che un tipo riferimento potrebbe essere null
o è sempre non null
. Si potrebbe pensare, "Ho pensato che tutti i tipi riferimento sono nullable!" Non è sbagliato ed è proprio così. Questa funzionalità consente di esprimere la finalità, che il compilatore tenta quindi di applicare. La stessa sintassi T?
indica che un tipo di riferimento può essere annullabile.
Si consideri il frammento di codice C# seguente:
#nullable enable
string first = string.Empty;
string second;
string? third;
Dato l'esempio precedente, il compilatore deduce la finalità come indicato di seguito:
first
non è mainull
poiché è assegnaton in maniera definitiva.second
non deve mai esserenull
, anche se inizialmente ènull
. Valutaresecond
prima di assegnare un valore genera un avviso del compilatore perché non è inizializzato.third
potrebbe esserenull
. Ad esempio, potrebbe puntare aSystem.String
, ma potrebbe puntare anull
. Una di queste varianti è accettabile. Il compilatore ti avvisa se dereferenzithird
senza prima verificare che non sia null.
Importante
Per utilizzare la funzionalità dei tipi di riferimento nullable, come illustrato in precedenza, questa deve trovarsi all'interno di un contesto nullable. Questa operazione è descritta in dettaglio nella sezione successiva.
Contesto nullable
I contesti nullable consentono il controllo con granularità fine di come il compilatore interpreta le variabili dei tipi riferimento. Esistono quattro possibili contesti nullable:
disable
: il compilatore si comporta in modo analogo a C# 7.3 e versioni precedenti.enable
: il compilatore abilita tutte le analisi dei riferimenti Null e tutte le funzionalità del linguaggio.warnings
: il compilatore esegue tutte le analisi Null e genera avvisi quando il codice potrebbe dereferenziarenull
.annotations
: Il compilatore non esegue l'analisi di nullità né genera avvisi quando il codice potrebbe dereferenziarenull
, ma è comunque possibile annotare il codice usando tipi riferimento nullabile?
e gli operatori null-forgiving (!
).
Questo modulo ha come ambito i contesti nullable disable
o enable
. Per altre informazioni, vedere Tipi riferimento Nullable: Contesto nullable.
Abilitare i tipi riferimento nullable
Nel file di progetto C# (con estensione csproj) aggiungere un nodo figlio <Nullable>
all'elemento <Project>
(o accodarlo a un oggetto esistente <PropertyGroup>
). Verrà applicato il contesto nullable enable
all'intero progetto.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Omitted for brevity -->
</Project>
In alternativa, è possibile definire l'ambito del contesto nullable in un file C# usando una direttiva del compilatore.
#nullable enable
La direttiva del compilatore C# precedente è funzionalmente equivalente alla configurazione del progetto, ma ha come ambito il file in cui si trova. Per ulteriori informazioni, vedere Tipi di riferimento annullabili: contesti annullabili (documentazione)
Importante
Il contesto nullable è abilitato nel file con estensione csproj per impostazione predefinita in tutti i modelli di progetto C# a partire da .NET 6.0 e versioni successive.
Quando il contesto nullable è abilitato, verranno visualizzati nuovi avvisi. Si consideri l'esempio precedente FooBar
, che prevede due avvisi quando viene analizzata in un contesto nullable:
La riga
FooBar fooBar = null;
contiene un avviso relativo all'assegnazionenull
: Avviso C# CS8600: Conversione del valore letterale Null o di un possibile valore Null in un tipo che non ammette i valori Null.La riga
_ = fooBar.ToString();
include anche un avviso. Questa volta il compilatore è preoccupato per il fatto chefooBar
può essere null: Avviso C# CS8602: Dereferenziazione di un riferimento possibilmente null.
Importante
Non esiste alcuna sicurezza null garantita, anche se si reagisce e si eliminano elimina tutti gli avvisi. Esistono alcuni scenari limitati che supereranno l'analisi del compilatore, ma genereranno un runtime NullReferenceException
.
Riepilogo
In questa unità si è appreso come abilitare un contesto nullable in C# per proteggersi da NullReferenceException
. Nell'unità successiva imparerai di più su come esprimere esplicitamente il tuo intento in un contesto nullable.