Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In un contesto abilitato nullable, il compilatore esegue l'analisi statica del codice per determinare lo stato Null di tutte le variabili di tipo riferimento:
- not-null: l'analisi statica determina che una variabile ha un valore non Null.
- maybe-null: l'analisi statica non è in grado di determinare che una variabile è assegnata a un valore non Null.
Questi stati consentono al compilatore di fornire avvisi quando è possibile dereferenziare un valore Null, generando un'eccezione System.NullReferenceException. Questi attributi forniscono al compilatore informazioni semantiche sullo stato Null degli argomenti, dei valori restituiti e dei membri dell'oggetto. Gli attributi chiarisce lo stato degli argomenti e restituiscono valori. Il compilatore fornisce avvisi più accurati quando le API vengono annotate correttamente con queste informazioni semantiche.
Questo articolo fornisce una breve descrizione di ognuno degli attributi del tipo riferimento nullable e come usarli.
Iniziamo con un esempio. Si supponga che la libreria abbia l'API seguente che recupera una stringa di risorsa. Questo metodo è stato originariamente compilato in un contesto oblivious nullable:
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
L'esempio precedente segue il modello familiare Try* in .NET. Esistono due parametri di riferimento per questa API: key e message. Questa API ha le regole seguenti relative allo stato Null di questi parametri:
- I chiamanti non devono passare
nullcome argomento perkey. - I chiamanti possono passare una variabile il cui valore è
nullcome argomento permessage. - Se il metodo
TryGetMessagerestituiscetrue, il valore dimessagenon è Null. Se il valore restituito èfalse, il valore dimessageè Null.
La regola per key può essere espressa in modo conciso: key deve essere un tipo riferimento non nullable. Il parametro message è più complesso. Consente una variabile che è null come argomento, ma garantisce che, in caso di esito positivo, l'argomento out non è null. Per questi scenari, è necessario un vocabolario più completo per descrivere le aspettative. L'attributo NotNullWhen descrive lo stato Null per l'argomento utilizzato per il message parametro .
Nota
L'aggiunta di questi attributi fornisce al compilatore altre informazioni sulle regole per l'API. Quando il codice chiamante viene compilato in un contesto abilitato nullable, il compilatore avvisa i chiamanti quando violano tali regole. Questi attributi non abilitano altri controlli sull'implementazione.
| Attributo | Category | Significato |
|---|---|---|
| AllowNull | Precondizione | Un parametro, un campo o una proprietà non nullable potrebbe essere Null. |
| DisallowNull | Precondizione | Un parametro, un campo o una proprietà nullable non deve mai essere Null. |
| MaybeNull | Postcondizione | Un parametro, un campo, una proprietà o un valore restituito non nullable potrebbe essere Null. |
| NotNull | Postcondizione | Un parametro nullable, un campo, una proprietà o un valore restituito non è mai null. |
| MaybeNullWhen | Postcondizione condizionale | Un argomento non nullable potrebbe essere Null quando il metodo restituisce il valore specificato bool . |
| NotNullWhen | Postcondizione condizionale | Un argomento nullable non è Null quando il metodo restituisce il valore specificato bool . |
| NotNullIfNotNull | Postcondizione condizionale | Un valore restituito, una proprietà o un argomento non è Null se l'argomento per il parametro specificato non è Null. |
| MemberNotNull | Metodi di supporto per metodi e proprietà | Il membro elencato non è Null quando il metodo restituisce. |
| MemberNotNullWhen | Metodi di supporto per metodi e proprietà | Il membro elencato non è Null quando il metodo restituisce il valore specificato bool . |
| DoesNotReturn | Codice non raggiungibile | Un metodo o una proprietà non restituisce mai. In altre parole, genera sempre un'eccezione. |
| DoesNotReturnIf | Codice non raggiungibile | Questo metodo o proprietà non restituisce mai se il parametro bool associato ha il valore specificato. |
Le descrizioni precedenti sono un riferimento rapido al funzionamento di ogni attributo. Le sezioni seguenti descrivono il comportamento e il significato di questi attributi in modo più approfondito.
Precondizioni: AllowNull e DisallowNull
Si consideri una proprietà di lettura/scrittura che non restituisce mai null perché ha un valore predefinito ragionevole. I chiamanti passano null alla funzione di accesso set quando lo impostano su tale valore predefinito. Si consideri, ad esempio, un sistema di messaggistica che richiede un nome di schermata in una chat room. Se non ne viene specificato nessuno, il sistema genera un nome casuale:
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;
Quando si compila il codice precedente in un contesto oblivious nullable, va tutto bene. Dopo aver abilitato i tipi riferimento nullable, la proprietà ScreenName diventa un riferimento non nullable. I chiamanti non devono controllare la proprietà restituita per null. Ma ora l'impostazione della proprietà su null genera un avviso. Per supportare questo tipo di codice, aggiungere l'attributo System.Diagnostics.CodeAnalysis.AllowNullAttribute alla proprietà, come illustrato nel codice seguente:
[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();
Potrebbe essere necessario aggiungere una using direttiva per System.Diagnostics.CodeAnalysis usare questo e altri attributi descritti in questo articolo. L'attributo viene applicato alla proprietà, non alla funzione di accesso set. L'attributo AllowNull specifica le pre-condizionie si applica solo agli argomenti. La funzione di accesso get ha un valore restituito, ma nessun parametro. Pertanto, l'attributo AllowNull si applica solo alla funzione di accesso set.
Nell'esempio precedente viene illustrato cosa cercare quando si aggiunge l'attributo AllowNull in un argomento:
- Il contratto generale per tale variabile è che non deve essere
null, quindi si vuole un tipo di riferimento non nullable. - Esistono scenari per il passaggio di
nullda parte di un chiamante come argomento, anche se non sono l'utilizzo più comune.
Nella maggior parte dei casi è necessario questo attributo per le proprietà, o ingli argomenti , oute ref . L'attributo AllowNull è la scelta migliore quando una variabile è in genere non Null, ma è necessario consentire null come precondizione.
In contrasto con gli scenari di utilizzo di DisallowNull: si usa questo attributo per specificare che un argomento di un tipo riferimento nullable non deve essere null. Si consideri una proprietà in cui null è il valore predefinito, ma i client possono impostarlo solo su un valore non Null. Osservare il codice seguente:
public string ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;
Il codice precedente è il modo migliore per esprimere la progettazione che ReviewComment potrebbe essere null, ma non può essere impostato su null. Una volta che questo codice è in grado di rendere nullable, è possibile esprimere questo concetto in modo più chiaro ai chiamanti che usano System.Diagnostics.CodeAnalysis.DisallowNullAttribute:
[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;
In un contesto che ammette i valori Null la funzione di accesso ReviewComment di get potrebbe restituire il valore predefinito di null. Il compilatore avvisa che deve essere controllato prima dell'accesso. Inoltre, avvisa i chiamanti che, anche se potrebbe essere null, i chiamanti non devono impostarlo in modo esplicito su null. L'attributo DisallowNull specifica anche una condizione preliminare, non influisce sulla funzione di accesso get. L'attributo DisallowNull viene usato quando si osservano queste caratteristiche relative a:
- La variabile può essere
nullnegli scenari principali, spesso quando viene creata una prima istanza. - La variabile non deve essere impostata in modo esplicito su
null.
Queste situazioni sono comuni nel codice originariamente null oblivious. Potrebbe essere che le proprietà dell'oggetto siano impostate in due operazioni di inizializzazione distinte. È possibile che alcune proprietà siano impostate solo dopo il completamento di alcune operazioni asincrone.
Gli AllowNull attributi e DisallowNull consentono di specificare che le precondizioni sulle variabili potrebbero non corrispondere alle annotazioni nullable per tali variabili. Queste annotazioni forniscono maggiori dettagli sulle caratteristiche dell'API. Queste informazioni aggiuntive consentono ai chiamanti di usare correttamente l'API. Tenere presente che si specificano precondizioni usando gli attributi seguenti:
- AllowNull: un argomento non nullable potrebbe essere Null.
- DisallowNull: un argomento nullable non deve mai essere null.
Postcondizioni: MaybeNull e NotNull
Si supponga di avere un metodo con la firma seguente:
public Customer FindCustomer(string lastName, string firstName)
È probabile che sia stato scritto un metodo simile al seguente per restituire null quando il nome cercato non è stato trovato.
null indica chiaramente che il record non è stato trovato. In questo esempio è probabile che si modifichi il tipo restituito da Customer a Customer?. La dichiarazione del valore restituito come tipo riferimento nullable specifica chiaramente la finalità di questa API:
public Customer? FindCustomer(string lastName, string firstName)
Per motivi trattati in Generics nullability che la tecnica potrebbe non produrre l'analisi statica corrispondente all'API. Potrebbe essere disponibile un metodo generico che segue un modello simile:
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Il metodo restituisce null quando l'elemento cercato non viene trovato. È possibile chiarire che il metodo restituisce null quando un elemento non viene trovato aggiungendo l'annotazione MaybeNull al metodo restituito:
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Il codice precedente informa i chiamanti che il valore restituito può effettivamente essere Null. Informa inoltre il compilatore che il metodo può restituire un'espressione null anche se il tipo non è nullable. Quando si dispone di un metodo generico che restituisce un'istanza del relativo parametro di tipo, T, è possibile esprimere che non restituisce mai null usando l'attributo NotNull.
È anche possibile specificare che un valore restituito o un argomento non è Null anche se il tipo è un tipo riferimento nullable. Il metodo seguente è un metodo helper che genera se il primo argomento è null:
public static void ThrowWhenNull(object value, string valueExpression = "")
{
if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}
È possibile chiamare questa routine come segue:
public static void LogMessage(string? message)
{
ThrowWhenNull(message, $"{nameof(message)} must not be null");
Console.WriteLine(message.Length);
}
Dopo aver abilitato i tipi riferimento Null, ci si vuole assicurare che il codice precedente venga compilato senza avvisi. Quando il metodo viene restituito, è garantito che il parametro value non è Null. Tuttavia, è accettabile chiamare ThrowWhenNull con un riferimento Null. È possibile rendere value un tipo riferimento nullable, e aggiungere la post-condizione NotNull alla dichiarazione di parametro:
public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
_ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
// other logic elided
Il codice precedente esprime chiaramente il contratto esistente: i chiamanti possono passare una variabile con il valore null, ma l'argomento non sarà mai null se il metodo restituisce senza generare un'eccezione.
È possibile specificare postcondizioni non condizionali usando gli attributi seguenti:
- ForseNull: un valore restituito non nullable può essere Null.
- NotNull: un valore restituito nullable non è mai null.
Post-condizioni condizionali: NotNullWhen, MaybeNullWhen e NotNullIfNotNull
Probabilmente si ha familiarità con il metodo stringString.IsNullOrEmpty(String). Questo metodo restituisce true quando l'argomento è null o una stringa vuota. Si tratta di una forma di controllo null: i chiamanti non devono controllare null-check l'argomento se il metodo restituisce false. Per rendere un metodo simile al seguente, è necessario impostare l'argomento su un tipo riferimento nullable e aggiungere l'attributo NotNullWhen:
bool IsNullOrEmpty([NotNullWhen(false)] string? value)
Questo informa il compilatore che qualsiasi codice in cui il valore restituito è false non ha bisogno di controlli su Null. L'aggiunta dell'attributo informa l'analisi statica del compilatore che IsNullOrEmpty esegue il controllo Null necessario: quando restituisce false, l'argomento non è null.
string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.
Il String.IsNullOrEmpty(String) metodo è come illustrato nell'esempio precedente. Nella codebase potrebbero essere presenti metodi simili che controllano lo stato degli oggetti per i valori Null. Il compilatore non riconosce metodi di controllo Null personalizzati ed è necessario aggiungere manualmente le annotazioni. Quando si aggiunge l'attributo, l'analisi statica del compilatore sa quando la variabile testata è selezionata come null.
Un altro uso per questi attributi è il modello di Try*. Le postcondizioni per ref e gli argomenti out vengono comunicati tramite il valore restituito. Si consideri questo metodo illustrato in precedenza (in un contesto disabilitato nullable):
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
Il metodo precedente segue un tipico idioma .NET: il valore restituito indica se message è stato impostato sul valore cercato o, se non viene trovato alcun messaggio, sul valore predefinito. Se il metodo restituisce true, il valore di message non è Null; in caso contrario, il metodo imposta message su Null.
In un contesto abilitato nullable è possibile comunicare tale linguaggio usando l'attributo NotNullWhen. Quando si annotano i parametri per i tipi riferimento nullable, rendere message un oggetto string? e aggiungere un attributo:
bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message is not null;
}
Nell'esempio precedente il valore di message è noto come non null quando TryGetMessage restituisce true. È consigliabile annotare metodi simili nella codebase nello stesso modo: gli argomenti possono essere uguali a null, e sono noti per non essere Null quando il metodo restituisce true.
Potrebbe essere necessario anche un attributo finale. A volte lo stato Null di un valore restituito dipende dallo stato Null di uno o più argomenti. Questi metodi restituiscono un valore non Null ogni volta che determinati argomenti non nullsono . Per annotare correttamente questi metodi, usare l'attributo NotNullIfNotNull. Si consideri il modello seguente:
string GetTopLevelDomainFromFullUrl(string url)
Se l'argomento url non è Null, l'output non è null. Dopo aver abilitato i riferimenti nullable, è necessario aggiungere altre annotazioni se l'API può accettare un argomento Null. È possibile annotare il tipo restituito come illustrato nel codice seguente:
string? GetTopLevelDomainFromFullUrl(string? url)
Questo funziona anche, ma spesso impone ai chiamanti di implementare controlli aggiuntivi null . Il contratto è che il valore restituito sarà null solo quando l'argomento url è null. Per esprimere tale contratto, è necessario annotare questo metodo come illustrato nel codice seguente:
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
Nell'esempio precedente viene usato l'operatore nameof per il parametro url. Il valore restituito e l'argomento sono stati entrambi annotati con l'oggetto ? che indica che uno dei due può essere null. L'attributo chiarisce ulteriormente che il valore restituito non è Null quando l'argomento url non nullè .
È possibile specificare delle postcondizioni condizionali usando questi attributi:
-
MaybeNullWhen: un argomento non nullable può essere Null quando il metodo restituisce il valore specificato
bool. -
NotNullWhen: un argomento nullable non è Null quando il metodo restituisce il valore specificato
bool. - NotNullIfNotNull: un valore restituito non è Null se l'argomento per il parametro specificato non è Null.
Metodi helper: MemberNotNull e MemberNotNullWhen
Questi attributi specificano la finalità quando si esegue il refactoring del codice comune dai costruttori nei metodi helper. Il compilatore C# analizza i costruttori e gli inizializzatori di campo per assicurarsi che tutti i campi di riferimento non nullable vengano inizializzati prima che ogni costruttore restituisca. Tuttavia, il compilatore C# non tiene traccia delle assegnazioni di campo tramite tutti i metodi helper. Il compilatore genera un avviso CS8618 quando i campi non vengono inizializzati direttamente nel costruttore, ma piuttosto in un metodo helper. Aggiungere l'oggetto MemberNotNullAttribute a una dichiarazione di metodo e specificare i campi inizializzati in un valore non Null nel metodo. Ad esempio, si consideri l'esempio seguente:
public class Container
{
private string _uniqueIdentifier; // must be initialized.
private string? _optionalMessage;
public Container()
{
Helper();
}
public Container(string message)
{
Helper();
_optionalMessage = message;
}
[MemberNotNull(nameof(_uniqueIdentifier))]
private void Helper()
{
_uniqueIdentifier = DateTime.Now.Ticks.ToString();
}
}
È possibile specificare più nomi di campo come argomenti per il costruttore dell'attributo MemberNotNull.
L'oggetto MemberNotNullWhenAttribute ha un argomento bool. Si usa MemberNotNullWhen in situazioni in cui il metodo helper restituisce un oggetto bool che indica se il metodo helper ha inizializzato i campi.
Arrestare l'analisi nullable quando viene generato un metodo chiamato
Alcuni metodi, in genere helper di eccezioni o altri metodi di utilità, escono sempre generando un'eccezione. In alternativa, un helper genera un'eccezione in base al valore di un argomento booleano.
Nel primo caso, è possibile aggiungere l'attributo DoesNotReturnAttribute alla dichiarazione del metodo. L'analisi dello stato Null del compilatore non controlla alcun codice in un metodo che segue una chiamata a un metodo annotato con DoesNotReturn. Si consideri il metodo seguente:
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}
public void SetState(object containedField)
{
if (containedField is null)
{
FailFast();
}
// containedField can't be null:
_field = containedField;
}
Il compilatore non genera avvisi dopo la chiamata a FailFast.
Nel secondo caso, si aggiunge l'attributo System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute a un parametro booleano del metodo. È possibile modificare l'esempio precedente come segue:
private void FailFastIf([DoesNotReturnIf(true)] bool isNull)
{
if (isNull)
{
throw new InvalidOperationException();
}
}
public void SetFieldState(object? containedField)
{
FailFastIf(containedField == null);
// No warning: containedField can't be null here:
_field = containedField;
}
Quando il valore dell'argomento corrisponde al valore del costruttore DoesNotReturnIf, il compilatore non esegue alcuna analisi dello stato Null dopo tale metodo.
Riepilogo
L'aggiunta di tipi riferimento nullable fornisce un vocabolario iniziale per descrivere le aspettative delle API per le variabili che potrebbero essere null. Gli attributi forniscono un vocabolario più completo per descrivere lo stato Null delle variabili come precondizioni e postcondizioni. Questi attributi descrivono in modo più chiaro le aspettative e offrono un'esperienza migliore per gli sviluppatori che usano le API.
Quando si aggiornano le librerie per un contesto nullable, aggiungere questi attributi per guidare gli utenti delle API all'utilizzo corretto. Questi attributi consentono di descrivere completamente lo stato Null degli argomenti e i valori restituiti.
- AllowNull: un campo, un parametro o una proprietà non nullable potrebbe essere Null.
- DisallowNull: un campo, un parametro o una proprietà nullable non deve mai essere Null.
- MaybeNull: un campo, un parametro, una proprietà o un valore restituito non nullable potrebbe essere Null.
- NotNull: un campo nullable, un parametro, una proprietà o un valore restituito non è mai null.
-
ForseNullWhen: un argomento non nullable potrebbe essere Null quando il metodo restituisce il valore specificato
bool. -
NotNullWhen: un argomento nullable non è Null quando il metodo restituisce il valore specificato
bool. - NotNullIfNotNull: un parametro, una proprietà o un valore restituito non è Null se l'argomento per il parametro specificato non è Null.
- DoesNotReturn: un metodo o una proprietà non restituisce mai. In altre parole, genera sempre un'eccezione.
-
DoesNotReturnIf: questo metodo o proprietà non restituisce mai se il parametro
boolassociato ha il valore specificato.