Esprimere la finalità
- 5 minuti
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
fooListviene assegnato il valore restituito daGetFooList. - Il compilatore genera un avviso in
fooList.Find(f => f.Name == "Bar");perché il valore assegnato afooListpotrebbe esserenull. - Supponendo che
fooListnon sianull,Findpotrebbe 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 comeIStateSalesTaxnullable. - All'interno del corpo del metodo
salesTaxviene 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 sourceche per impostazione predefinita ènull. -
_sourcecon 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
fooBarin modo condizionale, assegnando il risultato diToStringalla variabilestr.- La variabile
strè di tipostring?(stringa che ammette i valori Null).
- La variabile
- Scrive il valore di
strnell'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.