Febbraio 2018

Volume 33 Numero 2

Il presente articolo è stato tradotto automaticamente.

Concetti essenziali su .NET - C# 8.0 e tipi riferimento nullable

Da feed di Mark Michaelis | 2018 febbraio

I tipi di riferimento che ammette valori null, quali? Non tutti i tipi di riferimento sono nullable? 

Mi piace in c# ed è possibile individuare la progettazione del linguaggio attenzione molto utili. Tuttavia, attualmente è l'acronimo e anche dopo 7 versioni di c#, tuttavia non è un linguaggio perfetto. Da cui si intende che mentre è ragionevole aspettarsi probabilmente saranno sempre nuove funzionalità per aggiungere a c#, non esistono, inoltre, purtroppo, alcuni problemi. E, da problemi, questo non significa i bug ma, invece di problemi di base. Forse una delle principali aree di problemi e uno che è stata introdotta in c# 1.0-circonda il fatto che i tipi di riferimento possono essere null e, in effetti, i tipi di riferimento sono null per impostazione predefinita. Di seguito sono riportati alcuni dei motivi perché i tipi di riferimento che ammette valori null sono meno efficaci:

  • Richiamare un membro a un valore null genererà un'eccezione NullReferenceException, e ogni chiamata che comporta un'eccezione System. NullReferenceException nel codice di produzione è un bug. Purtroppo, tuttavia, con tipi di riferimento che ammette valori null è "rientrano nella" per eseguire l'elemento corretto anziché giusto. L'azione "rientrano" consiste nel richiamare un tipo di riferimento senza verificare la presenza di valori null.
  • Vi è un'incoerenza tra i tipi di riferimento e tipi di valore (dopo l'introduzione di Nullable < T >) in tipi di valore sono nullable quando decorata con "?" (ad esempio int? numero); in caso contrario, per impostazione predefinita a non nullable. Al contrario, i tipi di riferimento sono ammette valori null per impostazione predefinita. Questo è "normale" per coloro che l'ambito della programmazione in c# per molto tempo, ma se è stato possibile eseguire l'operazione tutto il mondo, sarebbe necessario il valore predefinito per tipi di riferimento per i valori non null e l'aggiunta di un "?" per essere in modo esplicito per consentire valori null.
  • Non è possibile eseguire l'analisi statica del flusso per controllare tutti i percorsi relativi che sarà un valore null prima di rimuovere il riferimento, o non. Si consideri, ad esempio, se si sono verificati codice non gestito, chiamate multi-threading, o null assegnazione/sostituzione in base alle condizioni di runtime. (Senza considerare se analisi include il controllo della raccolta di tutte le API che vengono richiamate.)
  • Non è disponibile alcuna sintassi ragionevole per indicare che non è valido per una dichiarazione specifica un valore di tipo riferimento null.
  • Non è possibile decorare parametri non sono consentiti valori null.

Come già detto, nonostante questo, amo c# per il punto di accettare semplicemente il comportamento di null come un idiosyncrasy di c#. Con c# 8.0, tuttavia, il team di linguaggio c# è l'impostazione con l'intenzione di migliorare la. In particolare, si spera di eseguire le operazioni seguenti:

  • Fornire la sintassi per prevedere null: Consentire allo sviluppatore di identificare in modo esplicito quando un tipo di riferimento deve contenere valori null, e, pertanto, non i casi di flag quando è in modo esplicito assegnato null.
  • Verificare i tipi di riferimento predefinito prevede che non ammette valori null: Modificare la prospettiva predefinita di tutti i tipi di riferimento per essere non nullable, ma con un'opzione del compilatore consenso esplicito anziché improvvisamente sovraccarico allo sviluppatore avvisi per il codice esistente.
  • Ridurre l'occorrenza delle eccezioni NullReferenceException: Ridurre la probabilità di eccezioni NullReferenceException migliorando l'analisi statica del flusso che consente di contrassegnare i possibili casi in cui un valore non è stato archiviato in modo esplicito i valori null prima di richiamare uno dei membri del valore.
  • Abilitare l'eliminazione dell'avviso di analisi del flusso statico: Supporta una qualche forma di "trust me, sono un programmatore" dichiarazione che consente allo sviluppatore di ignorare l'analisi statica del flusso del compilatore e, pertanto, eliminare qualsiasi avviso di un'eccezione NullReferenceException possibili.

Per il resto dell'articolo, si prendano in considerazione ognuno di questi obiettivi e come c# 8.0 implementa il supporto fondamentale relativa all'interno del linguaggio c#.

Fornire la sintassi per prevedere Null

Per iniziare, è necessario utilizzare una sintassi per distinguere quando un tipo di riferimento dovrebbero null e quando non consentito. Utilizza la sintassi ovvia per consentire a null il? una dichiarazione che ammette valori null, per un tipo di valore e un tipo di riferimento. Includendo il supporto sui tipi di riferimento, lo sviluppatore ha un modo per acconsentire esplicitamente i valori null, ad esempio:

string? text = null;

L'aggiunta di questa sintassi vengono illustrati motivi per cui il miglioramento nullable critico viene riepilogato con il nome apparentemente poco chiaro, "tipi di riferimento che ammette valori null". Non è perché è un nuovo tipo di dati riferimento ammette valori null, ma piuttosto che ora è esplicita, acconsentire esplicitamente: supporto per dati di tale tipo.

Data la sintassi di tipi di riferimento che ammette valori null, qual è la sintassi di tipo riferimento non nullable?  Durante questo:

string! text = "Inigo Montoya"

può sembrare la scelta ideale, introduce la domanda di cosa si intende semplicemente per:

string text = GetText();

È ancora tre dichiarazioni, vale a dire: i tipi di riferimento che ammette valori null, i tipi di riferimento che non ammette valori null e i tipi di riferimento a non-so? ugh, No.

In alternativa, è necessario è:

  • I tipi di riferimento che ammette valori null: stringa? testo = null.
  • I tipi di riferimento non nullable: stringa text = "Inigo Montoya"

Questo implica, naturalmente, una modifica di rilievo linguaggio in modo che i tipi di riferimento senza modificatore sono non nullable per impostazione predefinita.

Verificare i tipi di riferimento predefinito prevede che Non ammette valori null

Cambio di dichiarazioni di riferimento standard (Nessun modificatore nullable) per i valori non null è probabilmente la più difficile di tutti i requisiti per ridurre idiosyncrasy ammette valori null. Il fatto della questione è il testo della stringa di oggi, risultati in un tipo di riferimento chiamato testo che consente di testo possono essere null, si aspetta di testo possono essere null e, in effetti, per il testo di valori predefiniti null in molti casi, ad esempio con un campo o una matrice. Tuttavia, solo con tipi di valore, tipi di riferimento che ammette valori null devono essere l'eccezione, non il valore predefinito. Sarebbe preferibile se quando è assegnato il valore null per il testo o non è riuscito a inizializzare il testo a un valore diverso da null, il compilatore sarebbe flag qualsiasi dereferenziazione della variabile di testo (il compilatore già flag dereferenziazione di una variabile locale prima di essere inizializzata).

Sfortunatamente, ciò significa la modifica della lingua e il rilascio di un avviso quando si assegna null (stringa di testo = null, ad esempio) o assegnare un tipo di riferimento che ammette valori null (ad esempio stringa? testo = null; stringa moreText = testo;). Il primo dei quali (stringa di testo = null) è una modifica sostanziale. (Il rilascio di un avviso per un elemento che precedentemente non generato alcun messaggio di avviso è una modifica sostanziale).  Per evitare di sovraccaricare agli sviluppatori gli avvisi non appena si avvia utilizzando il compilatore c# 8.0, invece il supporto di valori null verrà disattivato per impostazione predefinita, pertanto nessuna modifica di rilievo. Per sfruttare i vantaggi di esso, pertanto, è necessario registrarsi abilitando la funzionalità. (Si noti tuttavia che nel riquadro di anteprima disponibile al momento della redazione del presente documento, itl.tc/csnrtp, ammissione di valori null è abilitata per impostazione predefinita.)

Naturalmente, dopo la funzionalità è abilitata, verranno visualizzati gli avvisi, presenta la scelta. Scegliere in modo esplicito se il tipo di riferimento deve consentire valori null, o non. In caso contrario, rimuovere l'assegnazione null, rimuovendo così il messaggio di avviso. Potenzialmente, tuttavia, ciò introduce un avviso in un secondo momento su perché la variabile non è stata assegnata ed è necessario assegnare un valore non null. In alternativa, se null viene utilizzato in modo esplicito (che rappresenta "sconosciuto", ad esempio), quindi modificare il tipo di dichiarazione per i valori null, come in:

string? text = null;

Ridurre l'occorrenza delle eccezioni NullReferenceException

Dato un modo per dichiarare i tipi che ammettono valori null o non nullable, è ora un massimo di analisi del flusso statico del compilatore per determinare quando la dichiarazione è potenzialmente violata. Durante la dichiarazione di un tipo di riferimento come nullable o evitare un'assegnazione di null a un tipo non nullable funzionerà, nuovi avvisi o errori apparire in un secondo momento nel codice. Come già menzionato, non ammette valori null riferimento tipi verranno generato un errore in un secondo momento nel codice se non viene mai assegnata alla variabile locale (questo è true per le variabili locali prima di c# 8.0). Al contrario, l'analisi del flusso statico contrassegnerà qualsiasi dereferenziare la chiamata di un tipo nullable per la quale non è stata rilevata un precedente controllo per i valori null e/o di qualsiasi assegnazione di valore nullable da un valore diverso da null. Figura 1 illustra alcuni esempi.

Figura 1 esempi di risultati dell'analisi del flusso statico

string text1 = null;
// Warning: Cannot convert null to non-nullable reference
string? text2 = null;
string text3 = text2;
// Warning: Possible null reference assignment
Console.WriteLine( text2.Length ); 
// Warning: Possible dereference of a null reference
if(text2 != null) { Console.WriteLine( text2.Length); }
// Allowed given check for null

In entrambi i casi, il risultato finale è una riduzione delle eccezioni NullReferenceException potenziali tramite analisi statica del flusso per verificare una finalità ammette valori null.

Come illustrato in precedenza, l'analisi statica del flusso è opportuno contrassegnare quando un tipo non nullable potrebbe essere assegnato null, sia direttamente oppure quando assegnato un tipo nullable. Questa operazione non infallibile. Ad esempio, se un metodo dichiara che restituisce un tipo di riferimento che non ammette valori null (ad esempio una libreria che non è ancora stato aggiornato con i modificatori di supporto di valori null) o si verifica uno che erroneamente restituisce null (ad esempio è stato ignorato un messaggio di avviso) o un'eccezione non irreversibile e un non esegue l'assegnazione previsto, è comunque possibile che un tipo di riferimento che non ammette valori null potrebbe finire con un valore null. Questo rappresenta un problema, ma il supporto per i tipi di riferimento che ammette valori null devono diminuire la probabilità di generare un'eccezione NullReferenceException, tuttavia non eliminarlo. (Questo è analogo a dalla fallibilità di controllo del compilatore quando viene assegnata una variabile). Analogamente, l'analisi statica del flusso non sempre riconosce che il codice, infatti, controllare i valori null prima di dereferenziare un valore. In realtà, l'analisi di flusso solo controlla il supporto di valori null all'interno di un corpo del metodo di parametri e variabili locali e sfrutta le firme di metodo e l'operatore per determinare la validità. Ad esempio, non, approfondita il corpo di un metodo denominato IsNullOrEmpty per eseguire l'analisi in se tale metodo correttamente i controlli per i valori null in modo che nessun ulteriore controllo null è necessaria.

Abilitare l'eliminazione dell'avviso di analisi del flusso statico

Dato possibili dalla fallibilità di analisi statica del flusso, cosa accade se il controllo null (ad esempio con una chiamata come oggetto. ReferenceEquals null (s) o stringa. IsNullOrEmpty()) non è riconosciuto dal compilatore? Quando il programmatore conosce meglio che non sarà un valore null, è possibile dereferenziare seguente il! operatore (ad esempio, testo!) come in:

string? text;...
if(object.ReferenceEquals(text, null))
{  var type = text!.GetType()
}

Senza il punto esclamativo, il compilatore genererà un avviso di una chiamata null possibili. Quando si assegna un valore che ammette valori null a un valore non nullable in modo analogo, è possibile decorare il valore assegnato con un punto esclamativo per informare il compilatore che si, il programmatore conosce meglio:

string moreText = text!;

In questo modo, è possibile ignorare l'analisi statica del flusso, esattamente come è possibile utilizzare un cast esplicito. Naturalmente, in fase di esecuzione continueranno a essere eseguiti verifica appropriati.

Conclusioni

L'introduzione del modificatore di supporto di valori null per i tipi di riferimento non introduce un nuovo tipo. Tipi di riferimento sono ancora ammette valori null e la compilazione la stringa? Restituisce IL che è ancora System. String. La differenza a livello di linguaggio intermedio è l'effetto dei tipi nullable di modificati con un attributo:

System.Runtime.CompilerServices.NullableAttribute

In questo modo, viene compilato a valle può continuare a sfruttare lo scopo dichiarato. Inoltre, supponendo che l'attributo è disponibile, precedenti versioni di c# possono comunque fare riferimento compilato c# 8.0 librerie, sebbene senza eventuali miglioramenti del supporto di valori null. Più importanti, ciò significa che le API esistenti (ad esempio, l'API .NET) possono essere aggiornate con i metadati che ammette valori null senza interrompere l'API. Inoltre, significa che non è supportato per l'overload in base al modificatore di supporto di valori null.

È una conseguenza ingrata su null migliorando la gestione in c# 8.0. La transizione di dichiarazioni che ammette valori null in genere non nullable introdurrà inizialmente un numero significativo di avvisi. Durante questo evento sfortunato, ritiene che è stato mantenuto un bilanciamento ragionevole tra oculare e migliorando uno è codice:

  • Avviso di rimuovere un'assegnazione di null a un tipo non nullable potenzialmente Elimina un bug perché è un valore non null non deve essere.
  • In alternativa, aggiunta di un modificatore nullable consente di migliorare il codice in più esplicito allo scopo previsto.
  • Nel corso del tempo che verrà dissolvere la mancata corrispondenza tra codice aggiornato ammette valori null e il codice precedente, diminuendo il NullReferenceException bug che consentono di verifica.
  • La funzionalità di supporto di valori null è disattivata per impostazione predefinita nei progetti esistenti in modo è possibile ritardare la gestione con fino a un momento di propria scelta. Alla fine si dispone di codice più affidabile. Per i casi in cui si conosce meglio rispetto al compilatore, è possibile utilizzare il! operatore (dichiarazione, "Trust me, sono un programmatore.") come un cast.

Ulteriori miglioramenti del linguaggio c# 8.0

Esistono tre principali altre aree di miglioramento presi in considerazione per c# 8.0:

Flussi asincroni: Supporto per flussi asincroni consente di attendere la sintassi per scorrere una raccolta di attività (attività < bool >). Ad esempio, è possibile richiamare

foreach await (var data in asyncStream)

il thread non verrà bloccata per le istruzioni che seguono await, ma invece "continuerà con" li dopo aver completato lo scorrimento. E l'iteratore restituirà l'elemento successivo su richiesta (la richiesta è una chiamata dell'attività < bool > MoveNextAsync sull'iteratore del flusso enumerabile) seguito da una chiamata a T corrente {get;}.

Implementazioni di interfaccia predefinito: Con c# è possibile implementare più interfacce di modo che le firme di ciascuna interfaccia vengono ereditate. Inoltre, è possibile fornire un'implementazione di un membro in una classe base in modo che tutte le classi derivate includano un'implementazione predefinita del membro. Purtroppo, cosa non è possibile è possibile implementare più interfacce anche le implementazioni predefinite dell'interfaccia, vale a dire l'ereditarietà multipla. Con l'introduzione delle implementazioni di interfaccia predefinito, è di superare la restrizione. Supponendo che sia possibile un'implementazione predefinita ragionevole, con c# 8.0 sarà possibile includere un'implementazione del membro predefinito (proprietà e metodi solo) e tutte le classi che implementa l'interfaccia dispongono di un'implementazione predefinita. Ereditarietà multipla potrebbe essere dei vantaggi, il miglioramento effettivo in questo modo è la possibilità di estendere le interfacce con altri membri senza introdurre una modifica di rilievo API. È possibile, ad esempio, aggiungere un metodo di conteggio IEnumerator < T > (sebbene implementarlo richiederebbe l'iterazione su tutti gli elementi nella raccolta) senza interrompere tutte le classi che ha implementato l'interfaccia. Si noti che questa funzionalità richiede una versione di framework corrispondente (un elemento che non è stato richiesto dal c# 2.0 e generics).

Estensione per tutti gli elementi: Con LINQ proviene l'introduzione dei metodi di estensione. Ricordo con dinner con Anders Hejlsberg al momento e viene chiesto sugli altri tipi di estensione, ad esempio le proprietà. SIG Hejlsberg informati me che è stato solo sta occupando il team cosa è stato necessario per l'implementazione di LINQ. A questo punto, 10 anni in un secondo momento, che viene valutata nuovamente presupposto e si sta considerando l'aggiunta di metodi di estensione per non solo le proprietà, ma anche per gli eventi, operatori e potenzialmente costruttori (di quest'ultimo viene aperto di alcuni interessanti modello di factory implementazioni). Un punto importante notare, specialmente quando si tratta di proprietà, è che i metodi di estensione vengono implementati in classi statiche e, di conseguenza, non è alcuno stato di istanza aggiuntive per il tipo esteso introdotto. Se è necessario tale stato, è necessario archiviarlo in una raccolta indicizzata dall'istanza del tipo esteso, per recuperare lo stato associato.


Mark Michaelisè fondatore di IntelliTect, in cui ha serve come responsabile dell'architettura tecnica e trainer. Per quasi due decenni è stato MVP di Microsoft e un direttore regionale Microsoft dal 2007. Michaelis serve più Microsoft software revisione team di progettazione, ad esempio c#, Microsoft Azure, SharePoint e Visual Studio ALM. Egli legge conferenze developer e scritto numerosi libri, tra cui la più recente, "essenziali c# 7.0 (6a edizione)" (itl.tc/EssentialCSharp). È possibile contattarlo su Facebook al facebook.com/Mark.Michaelis, nel suo blog al IntelliTect.com/Mark, su Twitter: @markmichaelis o tramite posta elettronica in Mark@IntelliTect.com.

Grazie ai seguenti esperti Microsoft per la revisione dell'articolo: Kevin Bost, Grant Ericson, Tom Faust, Mads Torgersen


Viene illustrato in questo articolo nel forum di MSDN Magazine