Esprimere la finalità

Completato

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 da GetFooList.
  • Il compilatore genera un avviso in fooList.Find(f => f.Name == "Bar"); perché il valore assegnato a fooList potrebbe essere null.
  • Supponendo che fooList non sia null, Find potrebbe restituire null, 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 come IStateSalesTax 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 come null, avrà un valore.

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 in new 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 di ToString alla variabile str.
    • La variabile str è di tipo string? (stringa che ammette i valori Null).
  • 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.

Verificare le conoscenze

1.

Qual è il valore default del tipo di riferimento string?

2.

Qual è il comportamento previsto della dereferenziazione di null?

3.

Cosa accade quando viene eseguito questo codice C# throw null;?

4.

Quale istruzione è più precisa per i tipi di riferimento nullable?