Tipi riferimento nullable (riferimenti per C#)

Nota

Questo articolo illustra i tipi riferimento nullable. È anche possibile dichiarare tipi valore nullable.

I tipi riferimento nullable sono disponibili nel codice che ha acconsentito esplicitamente a un contesto con riconoscimento nullable. I tipi riferimento nullable, gli avvisi di analisi statica Null e l'operatore null-forgiving sono funzionalità del linguaggio facoltative. Sono tutti disattivati per impostazione predefinita. Un contesto nullable viene controllato a livello di progetto usando le impostazioni di compilazione o nel codice usando pragmas.

Importante

Tutti i modelli di progetto a partire da .NET 6 (C# 10) abilitano il contesto nullable per il progetto. I progetti creati con i modelli precedenti non includono questo elemento, e queste funzionalità non sono disattivate a meno che non vengano abilitate nel file di progetto o non usino pragma.

In un contesto compatibile con valori Nullable:

  • Una variabile di un tipo riferimento T deve essere inizializzata con non Null e non può mai essere assegnata a un valore che potrebbe essere null.
  • Una variabile di un tipo riferimento T? può essere inizializzata con null o assegnata a null, ma deve essere verificata in base a null prima della de-referenziazione.
  • Una variabile m di tipo T? viene considerata non Null quando si applica l'operatore null-forgiving, come in m!.

Le distinzioni tra un tipo riferimento T non nullable e un tipo riferimento T? nullable vengono applicate dall'interpretazione del compilatore delle regole precedenti. Una variabile di tipo T e una variabile di tipo T? sono rappresentate dallo stesso tipo .NET. L'esempio seguente dichiara una stringa non nullable e una stringa nullable e quindi usa l'operatore null-forgiving per assegnare un valore a una stringa non nullable:

string notNull = "Hello";
string? nullable = default;
notNull = nullable!; // null forgiveness

Le variabili notNull e nullable sono entrambe rappresentate dal tipo String. Poiché i tipi non nullable e nullable sono entrambi archiviati nello stesso tipo, esistono diverse posizioni in cui non è consentito usare un tipo riferimento nullable. In generale, non è possibile usare un tipo riferimento nullable come classe di base o come interfaccia implementata. Non è possibile usare un tipo riferimento nullable in alcuna espressione di test di tipo o creazione di oggetti. Un tipo riferimento nullable non può essere il tipo di un'espressione di accesso ai membri. Gli esempi seguenti mostrano questi costrutti:

public MyClass : System.Object? // not allowed
{
}

var nullEmpty = System.String?.Empty; // Not allowed
var maybeObject = new object?(); // Not allowed
try
{
    if (thing is string? nullableString) // not allowed
        Console.WriteLine(nullableString);
} catch (Exception? e) // Not Allowed
{
    Console.WriteLine("error");
}

Riferimenti nullable e analisi statica

Gli esempi nella sezione precedente illustrano la natura dei tipi riferimento nullable. I tipi riferimento nullable non sono nuovi tipi di classe, ma piuttosto annotazioni sui tipi di riferimento esistenti. Il compilatore usa tali annotazioni per individuare potenziali errori di riferimento Null nel codice. Non esiste alcuna differenza di runtime tra un tipo riferimento non nullable e un tipo riferimento nullable. Il compilatore non aggiunge alcun controllo di runtime per i tipi di riferimento non nullable. I vantaggi sono nell'analisi in fase di compilazione. Il compilatore genera avvisi che consentono di trovare e correggere potenziali errori Null nel codice. Si dichiara la finalità e il compilatore avvisa quando il codice viola tale finalità.

In un contesto abilitato nullable, il compilatore esegue l'analisi statica delle variabili di qualsiasi tipo di riferimento, sia nullable che non nullable. Il compilatore tiene traccia dello stato Null di ogni variabile di riferimento come non Null o forse Null. Lo stato predefinito di un riferimento non nullable è non Null. Lo stato predefinito di un riferimento nullable è forse null.

I tipi riferimento non nullable devono essere sempre sicuri da dereferenziare perché il relativo stato Null è non Null. Per applicare tale regola, il compilatore genera avvisi se un tipo di riferimento non nullable non viene inizializzato in un valore non Null. Le variabili locali devono essere assegnate dove sono dichiarate. A ogni campo deve essere assegnato un valore non Null, in un inizializzatore di campo o in ogni costruttore. Il compilatore genera avvisi quando un riferimento non nullable viene assegnato a un riferimento il cui stato è forse Null. In genere, un riferimento non nullable è non Null e non viene generato alcun avviso quando tali variabili vengono dereferenziate.

Nota

Se si assegna un'espressione forse Null a un tipo riferimento non nullable, il compilatore genera un avviso. Il compilatore genera quindi avvisi per tale variabile fino a quando non viene assegnato a un'espressione non Null.

I tipi riferimento nullable possono essere inizializzati o assegnati a null. Pertanto, l'analisi statica deve determinare che una variabile è non Null prima che venga dereferenziata. Se un riferimento nullable viene determinato come forse Null, l'assegnazione a una variabile di riferimento non nullable genera un avviso del compilatore. La classe seguente mostra esempi di questi avvisi:

public class ProductDescription
{
    private string shortDescription;
    private string? detailedDescription;

    public ProductDescription() // Warning! shortDescription not initialized.
    {
    }

    public ProductDescription(string productDescription) =>
        this.shortDescription = productDescription;

    public void SetDescriptions(string productDescription, string? details=null)
    {
        shortDescription = productDescription;
        detailedDescription = details;
    }

    public string GetDescription()
    {
        if (detailedDescription.Length == 0) // Warning! dereference possible null
        {
            return shortDescription;
        }
        else
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
    }

    public string FullDescription()
    {
        if (detailedDescription == null)
        {
            return shortDescription;
        }
        else if (detailedDescription.Length > 0) // OK, detailedDescription can't be null.
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
        return shortDescription;
    }
}

Il frammento di codice seguente mostra dove il compilatore genera avvisi quando si usa questa classe:

string shortDescription = default; // Warning! non-nullable set to null;
var product = new ProductDescription(shortDescription); // Warning! static analysis knows shortDescription maybe null.

string description = "widget";
var item = new ProductDescription(description);

item.SetDescriptions(description, "These widgets will do everything.");

Negli esempi precedenti viene illustrato come l'analisi statica del compilatore determina lo stato Null delle variabili di riferimento. Il compilatore applica regole del linguaggio per controlli e assegnazioni Null per informarne l'analisi. Il compilatore non può fare ipotesi sulla semantica dei metodi o delle proprietà. Se si chiamano metodi che eseguono controlli Null, il compilatore non può sapere che tali metodi influiscono sullo stato Null di una variabile. Esistono attributi che è possibile aggiungere alle API per informare il compilatore sulla semantica degli argomenti e sui valori restituiti. Questi attributi sono stati applicati a molte API comuni nelle librerie .NET Core. Ad esempio, IsNullOrEmpty è stato aggiornato e il compilatore interpreta correttamente tale metodo come controllo Null. Per altre informazioni sugli attributi applicabili all'analisi statica con stato Null, vedere l'articolo sugli attributi Nullable.

Impostazione del contesto nullable

Esistono due modi per controllare il contesto nullable. A livello di progetto, è possibile aggiungere l'impostazione del progetto <Nullable>enable</Nullable>. In un singolo file di origine C# è possibile aggiungere il pragma #nullable enable per abilitare il contesto nullable. Vedere l'articolo sull'impostazione di una strategia nullable. Prima di .NET 6, i nuovi progetti usano il valore predefinito, <Nullable>disable</Nullable>. A partire da .NET 6, i nuovi progetti includono l'elemento <Nullable>enable</Nullable> nel file di progetto.

Specifiche del linguaggio C#

Per altre informazioni, vedere le proposte seguenti per la specifica del linguaggio C#:

Vedi anche