Esprimere la finalità
Nell'unità precedente si è appreso come il compilatore C# può eseguire l'analisi statica per proteggersi da NullReferenceException
. Si è anche appreso come abilitare un contesto che ammette i valori Null. Questa unità contiene altre informazioni su come esprimere esplicitamente la propria finalità all'interno di un contesto che ammette i valori Null.
Dichiarazione delle variabili
Quando il contesto che ammette i valori Null è abilitato, si ha una maggiore visibilità sul modo in cui il compilatore vede il codice. È possibile intervenire sugli avvisi generati da un contesto che ammette i valori Null e in questo modo definire in modo esplicito le proprie intenzioni. Se, ad esempio, si continua a esaminare il codice FooBar
e si analizzano la dichiarazione e l'assegnazione:
// Define as nullable
FooBar? fooBar = null;
Si noti che ?
è aggiunto a FooBar
. Ciò indica al compilatore che l'utente intende esplicitamente che fooBar
deve ammettere i valori Null. Se non si intende che fooBar
ammetta valori Null, ma si vuole comunque evitare l'avviso, tenere presente quanto segue:
// Define as non-nullable, but tell compiler to ignore warning
// Same as FooBar fooBar = default!;
FooBar fooBar = null!;
In questo esempio l'operatore null-forgiving (!
) viene aggiunto a null
, che indica al compilatore che si sta inizializzando in modo esplicito questa variabile come Null. Il compilatore non genererà alcun avviso relativo al fatto che questo riferimento è Null.
Se possibile, è consigliabile assegnare valori non-null
alle variabili che non ammettono i valori Null quando vengono dichiarate:
// Define as non-nullable, assign using 'new' keyword
FooBar fooBar = new(Id: 1, Name: "Foo");
Operatori
Come illustrato nell'unità precedente, C# definisce diversi operatori per esprimere la finalità per i tipi riferimento nullable.
Operatore null-forgiving (!
)
L'operatore null-forgiving (!
) è stato introdotto nella sezione precedente. Indica al compilatore di ignorare l'avviso CS8600. Questo è un modo per indicare al compilatore che l'utente sa cosa sta facendo, ma implica che l'utente deve effettivamente sapere cosa sta facendo.
Quando si inizializzano tipi non-nullable mentre è abilitato un contesto che ammette i valori Null, potrebbe essere necessario chiedere esplicitamente al compilatore di "perdonare" l'utente. Si consideri il codice di esempio seguente:
#nullable enable
using System.Collections.Generic;
var fooList = new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
FooBar fooBar = fooList.Find(f => f.Name == "Bar");
// The FooBar type definition for example.
record FooBar(int Id, string Name);
Nell'esempio precedente FooBar fooBar = fooList.Find(f => f.Name == "Bar");
genera un avviso CS8600 perché Find
potrebbe restituire null
. Questo possibile null
potrebbe essere assegnato a fooBar
, che non ammette i valori Null in questo contesto. In questo esempio un po' forzato si sa, tuttavia, che Find
non restituirà mai null
in forma scritta. È possibile esprimere questa finalità al compilatore con l'operatore null-forgiving:
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!;
Si noti che !
si trova alla fine di fooList.Find(f => f.Name == "Bar")
. Ciò indica al compilatore che l'utente sa che l'oggetto restituito dal metodo Find
potrebbe essere null
e che va bene.
È anche possibile applicare l'operatore null-forgiving a un oggetto non ancorato prima di una chiamata a un metodo o alla valutazione della proprietà. Ecco un altro esempio un po' forzato:
List<FooBar>? fooList = FooListFactory.GetFooList();
// Declare variable and assign it as null.
FooBar fooBar = fooList.Find(f => f.Name == "Bar")!; // generates warning
static class FooListFactory
{
public static List<FooBar>? GetFooList() =>
new List<FooBar>
{
new(Id: 1, Name: "Foo"),
new(Id: 2, Name: "Bar")
};
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
Nell'esempio precedente:
GetFooList
è un metodo statico che restituisce un tipo nullable,List<FooBar>?
.- A
fooList
viene assegnato il valore restituito daGetFooList
. - Il compilatore genera un avviso in
fooList.Find(f => f.Name == "Bar");
perché il valore assegnato afooList
potrebbe esserenull
. - Supponendo che
fooList
non sianull
,Find
potrebbe restituirenull
, ma è noto che non lo farà, quindi viene applicato l'operatore che accetta valori null.
È possibile applicare l'operatore null-forgiving a fooList
per disabilitare l'avviso:
FooBar fooBar = fooList!.Find(f => f.Name == "Bar")!;
Nota
È consigliabile usare l'operatore null-forgiving con attenzione. Se lo si usa semplicemente per ignorare un avviso si dice al compilatore di non aiutare l'utente a individuare possibili errori Null. Usarlo con moderazione e solo quando si è certi.
Per altre informazioni, vedere Operatore ! (null-forgiving) (Informazioni di riferimento su C#).
Operatore di coalescenza di valori Null (??
)
Quando si utilizzano tipi nullable, potrebbe essere necessario valutare se sono attualmente null
ed eseguire determinate azioni. Ad esempio, quando a un tipo nullable è stato assegnato null
oppure il tipo nullable non è stato inizializzato, potrebbe essere necessario assegnargli un valore diverso da Null. Qui è utile l'operatore di coalescenza di valori Null (??
).
Si consideri l'esempio seguente:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
salesTax ??= DefaultStateSalesTax.Value;
// Safely use salesTax object.
}
Nel codice C# precedente:
- Il parametro
salesTax
è definito comeIStateSalesTax
nullable. - All'interno del corpo del metodo
salesTax
viene assegnato in modo condizionale usando l'operatore di coalescenza di valori Null.- In questo modo si garantisce che se
salesTax
è stato passato comenull
, avrà un valore.
- In questo modo si garantisce che se
Suggerimento
Ciò equivale dal punto di vista funzionale al codice C# seguente:
public void CalculateSalesTax(IStateSalesTax? salesTax = null)
{
if (salesTax is null)
{
salesTax = DefaultStateSalesTax.Value;
}
// Safely use salesTax object.
}
Ecco un esempio di un altro linguaggio C# comune in cui può essere utile l'operatore di coalescenza di valori Null:
public sealed class Wrapper<T> where T : new()
{
private T _source;
// If given a source, wrap it. Otherwise, wrap a new source:
public Wrapper(T source = null) => _source = source ?? new T();
}
Il codice C# precedente:
- Definisce una classe wrapper generica, in cui il parametro di tipo generico è vincolato a
new()
. - Il costruttore accetta un parametro
T source
che per impostazione predefinita ènull
. _source
con wrapper viene inizializzato in modo condizionale innew T()
.
Per altre informazioni, vedere Operatori ?? e ??= (Informazioni di riferimento su C#).
Operatore condizionale Null (?.
)
Quando si usano tipi nullable, potrebbe essere necessario eseguire in modo condizionale delle azioni in base allo stato di un oggetto null
. Nell'unità precedente, ad esempio, il record FooBar
è stato usato per dimostrare NullReferenceException
dereferenziando null
. Ciò è stato causato dalla chiamata di ToString
. Si consideri lo stesso esempio, in cui ora si applica l'operatore condizionale Null:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
var str = fooBar?.ToString();
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
Il codice C# precedente:
- Dereferenzia
fooBar
in modo condizionale, assegnando il risultato diToString
alla variabilestr
.- La variabile
str
è di tipostring?
(stringa che ammette i valori Null).
- La variabile
- Scrive il valore di
str
nell'output standard, che non contiene nulla. - La chiamata a
Console.Write(null)
è valida, quindi non ci sono avvisi. - Si riceve un avviso se si chiama
Console.Write(str.Length)
perché si sta potenzialmente dereferenziando il valore Null.
Suggerimento
Ciò equivale dal punto di vista funzionale al codice C# seguente:
using System;
// Declare variable and assign it as null.
FooBar fooBar = null;
// Conditionally dereference variable.
string str = (fooBar is not null) ? fooBar.ToString() : default;
Console.Write(str);
// The FooBar type definition.
record FooBar(int Id, string Name);
È possibile combinare l'operatore per esprimere più dettagliatamente la finalità. Ad esempio, è possibile concatenare gli operatori ?.
e ??
:
FooBar fooBar = null;
var str = fooBar?.ToString() ?? "unknown";
Console.Write(str); // output: unknown
Per altre informazioni, vedere Operatori ?. e ?[] (operatori condizionali Null).
Riepilogo
In questa unità si è appreso come esprimere la finalità di supporto dei valori Null nel codice. Nell'unità successiva i concetti appresi verranno applicati a un progetto esistente.