Aggiornare una codebase con tipi riferimento nullable per migliorare gli avvisi di diagnostica Null

I tipi riferimento nullable consentono di dichiarare se alle variabili di un tipo riferimento deve essere assegnato o meno un valore null. L'analisi statica e gli avvisi del compilatore quando il codice potrebbe dereferenziare il valore null sono il vantaggio più importante di questa funzionalità. Dopo l'abilitazione, il compilatore genera avvisi che consentono di evitare di generare un'eccezione System.NullReferenceException durante l'esecuzione del codice.

Se la codebase è relativamente piccola, è possibile attivare la funzionalità nel progetto, risolvere gli avvisi e sfruttare i vantaggi della diagnostica migliorata. Le codebase più grandi possono richiedere un approccio più strutturato per risolvere gli avvisi nel tempo, abilitando la funzionalità per alcuni quando si risolvono avvisi in tipi o file diversi. Questo articolo descrive diverse strategie per aggiornare una codebase e i compromessi associati a queste strategie. Prima di iniziare la migrazione, leggere la panoramica concettuale dei tipi riferimento nullable, che illustra l'analisi statica del compilatore, i valori di null-statemaybe-null e not-null e le annotazioni nullable. Dopo aver acquisito familiarità con questi concetti e termini, è possibile eseguire la migrazione del codice.

Pianificare la migrazione

Indipendentemente dal modo in cui si aggiorna la codebase, l'obiettivo è che nel progetto siano abilitati avvisi e annotazioni nullable. Dopo aver raggiunto tale obiettivo, nel progetto si avrà l'impostazione <nullable>Enable</nullable>. Non sono necessarie le direttive del preprocessore per regolare le impostazioni altrove.

La prima scelta consiste nell'impostare il valore predefinito per il progetto. Le opzioni sono:

  1. Disabilitare nullable come impostazione predefinita: disable è l'impostazione predefinita se non si aggiunge un elemento Nullable al file di progetto. Usare questa impostazione predefinita quando non si aggiungono attivamente nuovi file alla codebase. L'attività principale consiste nell'aggiornare la libreria in modo da usare tipi riferimento nullable. L'uso di questo valore predefinito significa che l'utente aggiunge una direttiva del preprocessore nullable a ogni file durante l'aggiornamento del codice.
  2. Abilitare nullable come impostazione predefinita: impostare questo valore predefinito quando si sviluppano attivamente nuove funzionalità. Si vuole che tutto il nuovo codice tragga vantaggio dai tipi riferimento nullable e dall'analisi statica nullable. L'uso di questo valore predefinito significa che è necessario aggiungere #nullable disable all'inizio di ogni file. Queste direttive del preprocessore verranno rimosse durante la risoluzione degli avvisi in ogni file.
  3. Avvisi nullable come impostazione predefinita: scegliere questa impostazione predefinita per una migrazione in due fasi. Nella prima fase, risolvere gli avvisi. Nella seconda fase attivare le annotazioni per dichiarare lo stato null previsto di una variabile. L'uso di questo valore predefinito significa che è necessario aggiungere #nullable disable all'inizio di ogni file.
  4. Annotazioni nullable come impostazione predefinita. Annotare il codice prima di risolvere gli avvisi.

L'abilitazione di nullable come impostazione predefinita crea più lavoro iniziale per aggiungere le direttive del preprocessore a ogni file. Il vantaggio è che ogni nuovo file di codice aggiunto al progetto sarà abilitato per i valori null. Qualsiasi nuovo lavoro riconoscerà i valori null; è necessario aggiornare solo il codice esistente. La disabilitazione di nullable come impostazione predefinita funziona meglio se la libreria è stabile e l'obiettivo principale dello sviluppo consiste nell'adottare tipi riferimento nullable. I tipi riferimento nullable vengono attivati durante l'annotazione delle API. Al termine, si abilitano i tipi riferimento nullable per l'intero progetto. Quando si crea un nuovo file, è necessario aggiungere le direttive del preprocessore e renderle in grado di riconoscere i valori null. Se gli sviluppatori del team lo dimenticano, il nuovo codice è ora presente nel backlog del lavoro per rendere tutto il codice in grado di riconoscere i valori nulla.

La strategia che l'utente seleziona dipende dalla quantità di sviluppo attivo che si sta verificando nel progetto. Più maturo e stabile è il progetto, migliore sarà la seconda strategia. Più funzionalità vengono sviluppate, migliore sarà la prima strategia.

Importante

Il contesto che ammette i valori Null globale non si applica ai file di codice generati. Qualsiasi strategia si usi, il contesto nullable è disabilitato per qualsiasi file di origine contrassegnato come generato. Ciò significa che le API nei file generati non vengono annotate. Esistono quattro modi in cui un file viene contrassegnato come generato:

  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 preprocessore #nullable.

Informazioni su contesti e avvisi

L'abilitazione di avvisi e annotazioni controlla il modo in cui il compilatore visualizza tipi riferimento e il supporto dei valori Null. Ogni tipo ha uno dei tre supporti dei valori Null seguenti:

  • oblivious: tutti i tipi riferimento sono oblivious nullable quando il contesto di annotazione è disabilitato.
  • nonnullable: un tipo riferimento senza annotazioni, C, è nonnullable quando il contesto di annotazione è abilitato.
  • nullable: un tipo riferimento con annotazioni, C?, è nullable, ma può essere generato un avviso quando il contesto di annotazione è disabilitato. Le variabili dichiarate con var sono nullable quando il contesto di annotazione è abilitato.

Il compilatore genera avvisi in base al supporto dei valori Null seguente:

  • I tipi nonnullable generano avvisi se viene assegnato loro un valore potenziale null.
  • I tipi nullable generano avvisi se dereferenziati quando è presente un valore maybe-null.
  • I tipi oblivious generano avvisi se vengono dereferenziati quando è presente un valore maybe-null e il contesto di avviso è abilitato.

Ogni variabile ha uno stato nullable predefinito che dipende dal relativo supporto dei valori Null:

  • Le variabili nullable hanno un valore predefinito maybe-null per null-state.
  • Le variabili non-nullable hanno un valore predefinito not-null per null-state.
  • Le variabili oblivious nullable hanno un valore predefinito not-null per null-state.

Prima di abilitare i tipi riferimento nullable, tutte le dichiarazioni nella codebase sono nullable oblivious. Questo è importante perché significa che tutti i tipi riferimento hanno un valore predefinito not-null per null-state.

Risolvere gli avvisi

Se il progetto usa Entity Framework Core, è necessario leggere le relative indicazioni Uso dei tipi riferimento nullable.

Quando si avvia la migrazione, è consigliabile iniziare abilitando solo gli avvisi. Tutte le dichiarazioni rimangono nullable oblivious, ma vengono visualizzati avvisi se si dereferenzia un valore quando lo statonull-state diventa maybe-null. Quando si risolvono questi avvisi, l'utente confronta il valore Null in più posizioni e la codebase diventa più resiliente. Per informazioni sulle tecniche specifiche per situazioni diverse, vedere l'articolo sulle tecniche per risolvere gli avvisi nullable.

È possibile risolvere gli avvisi e abilitare le annotazioni in ogni file o classe prima di continuare con un altro codice. Tuttavia, è spesso più efficiente risolvere gli avvisi generati mentre il contesto è warnings prima di abilitare le annotazioni del tipo. In questo modo, tutti i tipi sono oblivious fino a quando non è stato risolto il primo set di avvisi.

Abilitare annotazioni del tipo

Dopo aver risolto il primo set di avvisi, è possibile abilitare il contesto di annotazione. Questo cambia i tipi riferimento da oblivious a nonnullable. Tutte le variabili dichiarate con var sono nullable. Questa modifica introduce spesso nuovi avvisi. Il primo passaggio nel risolvere gli avvisi del compilatore consiste nell'usare annotazioni ? sui parametri e sui tipi restituiti per indicare quando gli argomenti o i valori restituiti possono essere null. Quando si esegue questa attività, l'obiettivo non è solo correggere gli avvisi. L'obiettivo più importante è quello di far comprendere al compilatore la finalità per i potenziali valori Null.

Gli attributi estendono le annotazioni del tipo

Sono stati aggiunti diversi attributi per esprimere informazioni aggiuntive sullo stato Null delle variabili. Le regole per le API sono probabilmente più complesse di not-null o maybe-null per tutti i parametri e i valori restituiti. Molte API hanno regole più complesse per i casi in cui le variabili possono o meno essere null. In questi casi si useranno gli attributi per esprimere tali regole. Gli attributi che descrivono la semantica dell'API sono disponibili nell'articolo sugli attributi che influiscono sull'analisi nullable.

Passaggi successivi

Dopo aver risolto tutti gli avvisi successivamente all'abilitazione delle annotazioni, è possibile impostare il contesto predefinito per il progetto su abilitato. Se nel codice sono stati aggiunti pragma per i contesti di annotazione o avviso nullable, è possibile rimuoverli. Nel tempo, potrebbero essere visualizzati nuovi avvisi. È possibile scrivere il codice che introduce gli avvisi. Una dipendenza della libreria può essere aggiornata per i tipi riferimento nullable. Tali aggiornamenti modificheranno i tipi di tale libreria da nullable oblivious a nonnullable o nullable.

È anche possibile esplorare questi concetti nel modulo Learn sulla sicurezza dei valori Null in C#.