Tipi riferimento nullable (riferimenti per C#)

Nota

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

Usare i tipi riferimento nullable nel codice che si trovano in 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. Tutti sono disattivati per impostazione predefinita. È possibile controllare un contesto nullable a livello di progetto usando le impostazioni di compilazione o nel codice usando pragmas.

Il riferimento al linguaggio C# documenta la versione rilasciata più di recente del linguaggio C#. Contiene anche la documentazione iniziale per le funzionalità nelle anteprime pubbliche per la versione futura del linguaggio.

La documentazione identifica tutte le funzionalità introdotte nelle ultime tre versioni della lingua o nelle anteprime pubbliche correnti.

Suggerimento

Per trovare quando una funzionalità è stata introdotta per la prima volta in C#, vedere l'articolo sulla cronologia delle versioni del linguaggio C#.

Importante

Tutti i modelli di progetto abilitano il contesto annullabile 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:

  • È necessario inizializzare una variabile di un tipo riferimento T con un valore non Null e non è mai possibile assegnare un valore che potrebbe essere null.
  • È possibile inizializzare una variabile di un tipo T? riferimento con null o assegnare null, ma è necessario eseguirne null la verifica prima della dereferenziazione.
  • Quando si applica l'operatore null-forgiving a una variabile m di tipo T?, come in m!, la variabile viene considerata non null.

Il compilatore applica le distinzioni tra un tipo T riferimento non nullable e un tipo T? riferimento nullable usando le regole precedenti. Una variabile di tipo T e una variabile di tipo T? sono dello 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 entrambi usano il String tipo . Poiché i tipi non nullable e nullable usano entrambi lo stesso tipo, non è possibile usare un tipo riferimento nullable in diverse posizioni. In generale, non è possibile usare un tipo riferimento nullable come classe base o come interfaccia implementata. Non è possibile usare un tipo riferimento nullable in qualsiasi espressione di test di tipo o creazione di oggetti. Non è possibile usare un tipo riferimento nullable come tipo di 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à.

Importante

Le annotazioni di riferimento nullable non introducono modifiche di comportamento, ma altre librerie potrebbero usare la reflection per produrre un comportamento di runtime diverso per i tipi riferimento nullable e non nullable. È importante notare che Entity Framework Core legge gli attributi annullabili. Interpreta un riferimento "nullable" come valore facoltativo e un riferimento "non-nullable" come valore obbligatorio.

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. È necessario assegnare variabili locali in cui vengono 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 vengono generati avvisi quando si dereferenziano tali variabili.

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.

È possibile inizializzare o assegnare null a tipi riferimento nullable. Pertanto, l'analisi statica deve determinare che una variabile è non Null prima che venga dereferenziata. Se un riferimento nullable viene determinato come forse null, assegnandolo 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. È possibile aggiungere attributi alle API per informare il compilatore sulla semantica degli argomenti e dei valori restituiti. Molte API comuni nelle librerie .NET hanno questi attributi. Ad esempio, il compilatore interpreta IsNullOrEmpty correttamente come controllo Null. Per altre informazioni sugli attributi applicabili all'analisi statica con stato Null, vedere l'articolo sugli attributi Nullable.

Contesto nullable

Il contesto nullable determina il modo in cui il compilatore gestisce le annotazioni dei tipi di riferimento nullable e gli avvisi generati durante l'analisi statica dello stato Null. Il contesto nullable contiene due flag: l'impostazione di annotazione e l'impostazione di avviso .

Sia le impostazioni dell'annotazione che quelle di avviso sono disattivate di default per i progetti esistenti. A partire da .NET 6 (C# 10), entrambi i flag sono abilitati per impostazione predefinita per i progetti new. Il motivo di avere due flag distinti per il contesto di tipo nullable è semplificare la migrazione di progetti di grandi dimensioni che precedono l'introduzione di tipi di riferimento nullable.

Per i progetti di piccole dimensioni, è possibile abilitare tipi di riferimento nullable, correggere gli avvisi e continuare. Tuttavia, per progetti di grandi dimensioni e soluzioni multiprogetto, questo processo potrebbe generare un numero elevato di avvisi. È possibile usare i pragma per abilitare i tipi di riferimento annullabili per ogni file man mano che si inizia a usarli. Le nuove funzionalità che proteggono dalla generazione di un'eccezione System.NullReferenceException possono comportare interruzioni quando sono attivate in una codebase esistente:

  • Tutte le variabili di riferimento tipizzate in modo esplicito vengono interpretate come tipi di riferimento non annullabili.
  • Il significato del vincolo class in generics è cambiato in modo da indicare un tipo riferimento non-nullable.
  • I nuovi avvisi vengono generati a causa di queste nuove regole.

Il contesto di annotazione nullable determina il comportamento del compilatore. Esistono quattro combinazioni per le impostazioni del contesto annullabile :

  • entrambi disabilitati: il codice è nullable-oblivious. Disable corrisponde al comportamento prima dell'abilitazione dei tipi riferimento nullable, ad eccezione del fatto che la nuova sintassi genera avvisi anziché errori.
    • Gli avvisi nullable sono disabilitati.
    • Tutte le variabili di tipo di riferimento sono tipi di riferimento nullable.
    • L'uso del suffisso ? per dichiarare un tipo riferimento nullable genera un avviso.
    • È possibile usare l'operatore null-forgiving, !, ma non ha alcun effetto.
  • tutte e due le opzioni sono abilitate: il compilatore abilita tutte le analisi dei riferimenti null e tutte le funzionalità del linguaggio.
    • Tutti i nuovi avvisi di annullabilità (nullable) sono abilitati.
    • È possibile usare il suffisso ? per dichiarare un tipo riferimento nullable.
    • Le variabili di riferimento di tipo senza il suffisso ? sono riferimenti non annullabili.
    • L'operatore null-safe sopprime gli avvisi per una possibile dereferenziazione di null.
  • avviso abilitato: il compilatore esegue tutte le analisi null e genera avvisi quando il codice potrebbe dereferenziare null.
    • Tutti i nuovi avvisi di annullabilità (nullable) sono abilitati.
    • L'uso del suffisso ? per dichiarare un tipo riferimento nullable genera un avviso.
    • Tutte le variabili di tipo riferimento possono essere null. Tuttavia, i membri hanno lo stato null di not-null all'apertura di tutte le parentesi dei metodi, a meno che non siano dichiarati con il suffisso ?.
    • È possibile usare l'operatore null-forgiving !.
  • annotazioni abilitate: Il compilatore non genera avvisi quando il codice potrebbe dereferenziare nullo quando si assegna un'espressione potenzialmente nulla a una variabile non annullabile.
    • Tutti gli avvisi nullable sono disabilitati.
    • È possibile usare il suffisso ? per dichiarare un tipo riferimento nullable.
    • Le variabili di riferimento di tipo senza il suffisso ? sono riferimenti non annullabili.
    • È possibile usare l'operatore null-forgiving, !, ma non ha alcun effetto.

È possibile impostare il contesto di annotazione nullable e il contesto di avviso nullable per un progetto usando l'elemento<Nullable> nel file con estensione csproj. Questo elemento configura il modo in cui il compilatore interpreta i valori Null dei tipi e gli avvisi generati. La tabella seguente mostra i valori consentiti e riepiloga i contesti specificati.

Context Avvisi di dereferenziazione Avvisi di assegnazione Tipi di riferimento Suffisso ? Operatore !
disable Disattivato Disattivato Sono tutti annullabili Genera un avviso Non ha effetti
enable Enabled Enabled Non-nullable, a meno che dichiarato con ? Dichiara un tipo nullable Elimina gli avvisi per l'eventuale assegnazione null
warnings Enabled Non applicabile Tutti sono nullable, ma i membri vengono considerati not-null alla parentesi aperta dei metodi Genera un avviso Elimina gli avvisi per l'eventuale assegnazione null
annotations Disattivato Disattivato Non-nullable, a meno che dichiarato con ? Dichiara un tipo nullable Non ha effetti

Le variabili di tipo riferimento nel codice compilato in un contesto disabled sono nullable-oblivious. È possibile assegnare un valore letterale null o una variabile maybe-null a una variabile che sia nullable-oblivious. Tuttavia, lo stato predefinito di una variabile nullable-oblivious è not-null.

Scegliere l'impostazione più adatta al progetto:

  • Scegliere disable per i progetti legacy che non si vuole aggiornare in base alla diagnostica o alle nuove funzionalità.
  • Scegliere warnings per determinare dove il codice potrebbe generare System.NullReferenceException. È possibile risolvere questi avvisi prima di modificare il codice per abilitare i tipi riferimento non-nullable.
  • Scegliere annotations per esprimere la finalità di progettazione prima di abilitare gli avvisi.
  • Scegliere enable per i nuovi progetti e i progetti attivi in cui ci si vuole proteggere da eccezioni di riferimento null.

Esempio:

<Nullable>enable</Nullable>

È anche possibile usare le direttive per impostare questi stessi flag ovunque nel codice sorgente. Le direttive sono particolarmente utili quando si esegue la migrazione di una codebase di grandi dimensioni.

  • #nullable enable: imposta i flag di annotazione e avviso su abilitare.
  • #nullable disable: imposta i flag di annotazione e avviso su per disabilitare.
  • #nullable restore: ripristina il flag di annotazione e il flag di avviso nelle impostazioni del progetto.
  • #nullable disable warnings: imposta il flag di avviso da disabilitare.
  • #nullable enable warnings: imposta il flag di avviso da abilitare.
  • #nullable restore warnings: ripristina l'indicatore di avviso nelle impostazioni del progetto.
  • #nullable disable annotations: imposta il flag di annotazione da disabilitare.
  • #nullable enable annotations: imposta il flag di annotazione da abilitare.
  • #nullable restore annotations: ripristina il flag di annotazione nelle impostazioni del progetto.

Per qualsiasi riga di codice, è possibile impostare una delle combinazioni seguenti:

Bandiera di avvertimento Indicatore di annotazione Utilizzo
impostazioni predefinite del progetto impostazioni predefinite del progetto Impostazione predefinita
abilitare disable Correggere gli avvisi di analisi
abilitare impostazioni predefinite del progetto Correggere gli avvisi di analisi
impostazioni predefinite del progetto abilitare Aggiungere annotazioni di tipo
abilitare abilitare Migrazione del codice già eseguita
disable abilitare Annotare il codice prima di correggere gli avvisi
disable disable Aggiunta di codice legacy al progetto migrato
impostazioni predefinite del progetto disable Raramente
disable impostazioni predefinite del progetto Raramente

Queste nove combinazioni offrono un controllo granulare sulla diagnostica che il compilatore genera per il codice. È possibile abilitare altre funzionalità in qualsiasi area che l'utente sta aggiornando, senza visualizzare altri avvisi che non è ancora pronto a risolvere.

Importante

Il contesto nullable globale non si applica ai file di codice generati. In entrambe le strategie, il contesto nullable è disabilitato per qualsiasi file di origine contrassegnato come generato. Questa condizione indica che il compilatore non annota alcuna API nei file generati. Il compilatore non genera avvisi nullable per i file generati. Un file viene contrassegnato come generato in uno dei quattro modi seguenti:

  1. In .editorconfig specificare generated_code = true in una sezione che si applica a tale file.
  2. Inserire <auto-generated> o <auto-generated/> in un commento all'inizio del file. Può trovarsi in qualsiasi riga di tale commento, ma il blocco di commento deve essere il primo elemento del file.
  3. Iniziare il nome del file con TemporaryGeneratedFile_
  4. Terminare il nome del file con .designer.cs, .generated.cs, .g.cs o .g.i.cs.

I generatori possono acconsentire esplicitamente usando la direttiva del #nullable preprocessore.

Per impostazione predefinita, le annotazioni e i flag di avviso nullable vengono disabilitati. Questo valore predefinito significa che il codice esistente viene compilato senza modifiche e senza generare nuovi avvisi. A partire da .NET 6, i nuovi progetti includono l'elemento <Nullable>enable</Nullable> in tutti i modelli di progetto, impostando questi flag su enabled.

Queste opzioni forniscono due strategie distinte per aggiornare una codebase esistente per l'uso di tipi riferimento nullable.

Impostazione del contesto nullable

È possibile controllare il contesto nullable in due modi. A livello di progetto, aggiungere l'impostazione del <Nullable>enable</Nullable> progetto. In un singolo file di origine C# aggiungere il #nullable enable pragma per abilitare il contesto nullable. Per altre informazioni, vedere 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.

Generici

Quando si usa un parametro di tipo, , Tcome controparte nullable, T?, l'argomento di tipo effettivo determina la modalità di interpretazione dell'oggetto ? . Si consideri la dichiarazione generica seguente:

public class Box<T>
{
    public T Contents { get; set; }
}

Poiché un parametro di tipo può essere impostato per un tipo riferimento o un tipo valore, il significato di T? dipende dall'argomento di tipo fornito dal chiamante. Le regole seguenti descrivono ciò che T? viene risolto quando T non ha vincoli:

  • L'argomento type è un tipo riferimento non nullable. Per Box<string>è Tstring e T? è string?, ovvero il tipo di riferimento nullable corrispondente.
  • L'argomento type è un tipo valore. Per Box<int>, T è ed T? è int anche intlo stesso tipo di valore. L'annotazione non ha alcun effetto sui tipi valore, a meno che il parametro di tipo non abbia il struct vincolo , nel qual caso T? significa Nullable<T> (int?).
  • L'argomento di tipo è già nullable. Per Box<string?>, T è string? e T? è ancora string?. Non si ottiene un tipo "doubly nullable".

I vincoli limitano gli argomenti di tipo consentiti. Consentono anche al compilatore di ragionare su come T usare:

  • where T : class richiede un tipo riferimento non nullable. Box<string> è consentito; Box<string?> genera un avviso.
  • where T : class? consente un tipo riferimento nullable o non nullable. Sia Box<string> che Box<string?> sono consentiti.
  • where T : struct richiede un tipo di valore non nullable. Box<int> è consentito; Box<int?> Non. Con questo vincolo, T? all'interno dei mezzi Nullable<T>generici , per Box<int>è T?int?.
  • where T : notnull richiede un riferimento o un tipo valore non nullable. Box<string> e Box<int> sono consentiti; Box<string?> genera un avviso.
  • where T : BaseType richiede un tipo riferimento non nullable che deriva da BaseType. Append ? (where T : BaseType?) per consentire anche tipi derivati nullable.

I vincoli consentono al compilatore di determinare il modo in cui viene usato un parametro di tipo generico:

public static T? FirstOrDefault<T>(IEnumerable<T> source)
{
    foreach (T item in source)
    {
        return item;
    }
    return default;
}

public static void RequireNotNull<T>(T value) where T : notnull
{
    ArgumentNullException.ThrowIfNull(value);
}

public static void Generics()
{
    string? first = FirstOrDefault<string>([]);
    Console.WriteLine(first ?? "<empty>");

    RequireNotNull("not null");
}

Specifiche del linguaggio C#

Per altre informazioni, vedere la sezione Tipi di riferimento Nullable della specifica del linguaggio C#.

Vedi anche