Informazioni sul supporto dei valori Null
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 processo per gli strumenti e il compilatore C# è a disposizione per agevolare il compito.
Definizione della sicurezza Null
Il termine sicurezza Null definisce un set di funzionalità specifiche per i tipi nullable 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 nullable. Prima di discutere del contesto nullable, vengono illustrati i possibili tipi nullable.
Tipi nullable
Prima di C# 2.0, solo i tipi riferimento erano nullable. Tipi di valore come int
o DateTime
non possono essere null
. Se questi tipi vengono inizializzati senza un valore, viene eseguito il fallback al relativo 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 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
. È possibile che si pensi "Credevo che tutti i tipi di riferimento ammettessero valori Null!". Non è sbagliato ed è proprio così. Questa funzionalità consente di esprimere la finalità, che il compilatore tenta quindi di applicare. La stessa sintassi T?
esprime che un tipo riferimento deve essere nullable.
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é è sicuramente assegnato.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 consente di avvisare se si dereferenziathird
senza prima verificare che non sia Null.
Importante
Per usare la funzionalità dei tipi riferimento nullable, come illustrato in precedenza, 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 Null o genera avvisi quando il codice potrebbe dereferenziarenull
, ma è comunque possibile annotare il codice usando tipi riferimento nullable?
e gli operatori di forgiving null (!
).
Questo modulo ha come ambito i contesti nullable disable
o enable
. Per altre informazioni, vedere Tipi riferimento Nullable: contesti 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 altre informazioni, vedere Tipi riferimento Nullable: contesti nullable (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 per l'assegnazionenull
: Avviso C# CS8600: Conversione di valori letterali Null o valore null possibile in un tipo non nullable.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 verranno fornite altre informazioni su come esprimere esplicitamente la propria finalità all'interno di un contesto che ammette i valori Null.