Atributy statické analýzy stavu null interpretované kompilátorem jazyka C#
V kontextu s povolenou hodnotou null kompilátor provede statickou analýzu kódu a určí stav null všech proměnných typu odkazu:
- not-null: Statická analýza určuje, že proměnná má nenulovou hodnotu.
- možná-null: Statická analýza nemůže určit, že je proměnná přiřazena nenulovou hodnotou.
Tyto stavy umožňují kompilátoru poskytovat upozornění, když můžete dereference hodnoty null vyvolání System.NullReferenceException. Tyto atributy poskytují kompilátoru sémantické informace o stavu null argumentů, návratových hodnot a členů objektů na základě stavu argumentů a návratových hodnot. Kompilátor poskytuje přesnější upozornění, když vaše rozhraní API byla správně opatřena poznámkami s touto sémantickou informací.
Tento článek obsahuje stručný popis jednotlivých atributů referenčního typu s možnou hodnotou null a jejich použití.
Začněme příkladem. Představte si, že vaše knihovna má k načtení řetězce prostředků následující rozhraní API. Tato metoda byla původně zkompilována v nezapomnělém kontextu s možnou hodnotou null:
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
Předchozí příklad se řídí známým Try*
vzorem v .NET. Pro toto rozhraní API existují dva referenční parametry: a key
message
. Toto rozhraní API má následující pravidla týkající se stavu null těchto parametrů:
- Volající by neměli předávat
null
jako argument prokey
. - Volající mohou předat proměnnou, jejíž hodnota je
null
jako argument promessage
. - Pokud metoda
TryGetMessage
vrátítrue
, hodnotamessage
není null. Pokud jefalse
vrácená hodnota , hodnotamessage
je null.
Pravidlo key
lze vyjádřit stručně: key
by měl být nenulový odkazový typ. Parametr message
je složitější. Umožňuje proměnnou, která je null
jako argument, ale zaručuje úspěch, že out
argument není null
. V těchto scénářích potřebujete bohatší slovní zásobu, která popisuje očekávání. Atribut NotNullWhen
popsaný níže popisuje stav null pro argument použitý pro message
parametr.
Poznámka:
Přidáním těchto atributů získáte kompilátoru další informace o pravidlech pro vaše rozhraní API. Při volání kódu je zkompilován v kontextu s povolenou hodnotou null, kompilátor upozorní volající při porušení těchto pravidel. Tyto atributy neumožňují další kontroly vaší implementace.
Atribut | Kategorie | Význam |
---|---|---|
AllowNull | Předběžná podmínka | Parametr, pole nebo vlastnost, která není null, může mít hodnotu null. |
Zakázatnull | Předběžná podmínka | Parametr, pole nebo vlastnost s možnou hodnotou null by nikdy neměl být null. |
MožnáNull | Následná podmínka | Parametr, pole, vlastnost nebo návratová hodnota, která není null, může být null. |
NotNull | Následná podmínka | Parametr, pole, vlastnost nebo návratová hodnota s možnou hodnotou null nikdy nebude. |
MožnáNullWhen | Podmíněná podmínka | Pokud metoda vrátí zadanou bool hodnotu, může být argument nenulový. |
NotNullWhen | Podmíněná podmínka | Argument s možnou hodnotou null nebude null, pokud metoda vrátí zadanou bool hodnotu. |
NotNullIfNotNull | Podmíněná podmínka | Návratová hodnota, vlastnost nebo argument není null, pokud argument pro zadaný parametr nemá hodnotu null. |
MemberNotNull | Pomocné metody metod metod a vlastností | Uvedený člen nebude mít při vrácení metody hodnotu null. |
MemberNotNullWhen | Pomocné metody metod metod a vlastností | Uvedený člen nebude mít hodnotu null, pokud metoda vrátí zadanou bool hodnotu. |
DoesNotReturn | Nedostupný kód | Metoda nebo vlastnost nikdy nevrací. Jinými slovy, vždy vyvolá výjimku. |
DoesNotReturnIf | Nedostupný kód | Tato metoda nebo vlastnost nikdy nevrátí, pokud přidružený bool parametr má zadanou hodnotu. |
Předchozí popisy jsou stručným odkazem na to, co každý atribut dělá. Následující části popisují chování a význam těchto atributů důkladněji.
Předpoklady: AllowNull
a DisallowNull
Zvažte vlastnost pro čtení a zápis, která se nikdy nevrátí null
, protože má rozumnou výchozí hodnotu. Volajícím se při nastavování této výchozí hodnoty předají null
přístupové objekty set. Představte si například systém zasílání zpráv, který žádá o název obrazovky v chatovací místnosti. Pokud žádný není zadaný, systém vygeneruje náhodný název:
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;
Když zkompilujete předchozí kód v nezapomnělém kontextu s možnou hodnotou null, je všechno v pořádku. Jakmile povolíte odkazové typy s možnou ScreenName
hodnotou null, vlastnost se stane nenulovým odkazem. To je správné pro get
příslušenství: nikdy nevrátí null
. Volající nemusí zkontrolovat vrácenou vlastnost null
. Teď ale nastavíte vlastnost tak, aby vygenerovala null
upozornění. Chcete-li podporovat tento typ kódu, přidejte System.Diagnostics.CodeAnalysis.AllowNullAttribute atribut do vlastnosti, jak je znázorněno v následujícím kódu:
[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();
Možná budete muset přidat direktivu using
pro System.Diagnostics.CodeAnalysis použití tohoto a dalších atributů, které jsou popsány v tomto článku. Atribut se použije u vlastnosti, nikoli u přístupového objektu set
. Atribut AllowNull
určuje předběžné podmínky a vztahuje se pouze na argumenty. Přístupová get
objekt má návratovou hodnotu, ale žádné parametry. AllowNull
Atribut se proto vztahuje pouze na set
přístup.
Předchozí příklad ukazuje, co hledat při přidání atributu do argumentu AllowNull
:
- Obecný kontrakt pro tuto proměnnou je, že by neměl být
null
, takže chcete typ odkazu bez hodnoty null. - Existují scénáře, kdy volající předá
null
jako argument, i když nejsou nejběžnějším využitím.
Nejčastěji budete tento atribut potřebovat pro vlastnosti, nebo in
out
, a ref
argumenty. Atribut AllowNull
je nejlepší volbou, pokud je proměnná obvykle nenulová, ale musíte ji povolit null
jako předběžnou podmínku.
Naproti tomu se scénáři použití DisallowNull
: Pomocí tohoto atributu určíte, že argument typu odkazu s možnou hodnotou null by neměl být null
. Představte si vlastnost, ve které null
je výchozí hodnota, ale klienti ji mohou nastavit pouze na hodnotu, která není null. Uvažujte následující kód:
public string ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;
Předchozí kód je nejlepší způsob, jak vyjádřit návrh, který ReviewComment
by mohl být null
, ale nelze jej nastavit na null
. Jakmile je tento kód s možnou hodnotou null, můžete tento koncept jasněji vyjádřit volajícím pomocí :System.Diagnostics.CodeAnalysis.DisallowNullAttribute
[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;
V kontextu s možnou ReviewComment
get
hodnotou null by přístupový objekt mohl vrátit výchozí hodnotu null
. Kompilátor varuje, že před přístupem musí být kontrolován. Kromě toho varuje volající, že i když by to mohlo být null
, volající by neměl explicitně nastavit na null
. Atribut DisallowNull
také určuje podmínku , která nemá vliv na get
přístupové objekty. Atribut použijete DisallowNull
při sledování těchto charakteristik:
- Proměnná může být
null
v základních scénářích, často při první instanci. - Proměnná by neměla být explicitně nastavená na
null
.
Tyto situace jsou běžné v kódu, který byl původně prázdný. Může se jednat o to, že vlastnosti objektu jsou nastaveny ve dvou různých inicializačních operacích. Může se stát, že některé vlastnosti se nastaví až po dokončení některé asynchronní práce.
DisallowNull
Atributy AllowNull
umožňují určit, že předběžné podmínky proměnných nemusí odpovídat poznámce s možnou hodnotou null u těchto proměnných. Ty poskytují podrobnější informace o vlastnostech vašeho rozhraní API. Tyto další informace pomáhají volajícím správně používat vaše rozhraní API. Nezapomeňte zadat předpoklady pomocí následujících atributů:
- AllowNull: Argument bez hodnoty null může být null.
- DisallowNull: Argument s možnou hodnotou null by nikdy neměl být null.
Postconditions: MaybeNull
a NotNull
Předpokládejme, že máte metodu s následujícím podpisem:
public Customer FindCustomer(string lastName, string firstName)
Pravděpodobně jste napsali takovou metodu, která se vrátí null
, když nebyl nalezen název. Jasně null
značí, že záznam nebyl nalezen. V tomto příkladu byste pravděpodobně změnili návratový typ z Customer
na Customer?
. Deklarace návratové hodnoty jako typu odkazu s možnou hodnotou null určuje záměr tohoto rozhraní API jasně:
public Customer? FindCustomer(string lastName, string firstName)
Z důvodů popsaných v části Obecná dostupnost null tato technika nemusí způsobit statickou analýzu, která odpovídá vašemu rozhraní API. Můžete mít obecnou metodu, která se řídí podobným vzorem:
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Metoda vrátí null
, když hledané položky nebyla nalezena. Můžete objasnit, že metoda vrátí null
, když položka není nalezena přidáním poznámky MaybeNull
do metody return:
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Předchozí kód informuje volající, že návratová hodnota může být ve skutečnosti null. Informuje také kompilátor, že metoda může vrátit null
výraz, i když typ není nullable. Pokud máte obecnou metodu, která vrací instanci parametru typu, T
můžete vyjádřit, že se nikdy nevrátí null
pomocí atributu NotNull
.
Můžete také určit, že návratová hodnota nebo argument není null, i když je typ typu odkaz s možnou hodnotou null. Následující metoda je pomocná metoda, která vyvolá, pokud je jeho první argument:null
public static void ThrowWhenNull(object value, string valueExpression = "")
{
if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}
Tuto rutinu můžete volat takto:
public static void LogMessage(string? message)
{
ThrowWhenNull(message, $"{nameof(message)} must not be null");
Console.WriteLine(message.Length);
}
Po povolení typů odkazů s hodnotou null chcete zajistit, aby se předchozí kód zkompiluje bez upozornění. Když metoda vrátí, je zaručeno, value
že parametr nemá hodnotu null. Je však přijatelné volat ThrowWhenNull
s nulovým odkazem. Můžete vytvořit value
typ odkazu s možnou hodnotou null a přidat NotNull
post-condition do deklarace parametru:
public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
_ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
// other logic elided
Předchozí kód jasně vyjadřuje existující kontrakt: Volající mohou předat proměnnou s null
hodnotou, ale argument je zaručen, že nikdy nebude null, pokud metoda vrátí bez vyvolání výjimky.
Nepodmíněné postconditions zadáte pomocí následujících atributů:
- MožnáNull: Návratová hodnota, která není null, může mít hodnotu null.
- NotNull: Návratová hodnota s možnou hodnotou null nikdy nebude null.
Podmíněné po podmínkách: NotNullWhen
, MaybeNullWhen
a NotNullIfNotNull
Pravděpodobně znáte metodu string
String.IsNullOrEmpty(String). Tato metoda vrátí true
, pokud je argument null nebo prázdný řetězec. Jedná se o formu kontroly null: Volající nemusí argument null-check argument, pokud metoda vrátí false
. Pokud chcete, aby metoda, jako je tato s možnou hodnotou null, nastavili byste argument na typ odkazu s možnou NotNullWhen
hodnotou null a přidejte atribut:
bool IsNullOrEmpty([NotNullWhen(false)] string? value)
Informuje kompilátor, že jakýkoli kód, ve kterém je false
vrácená hodnota, nepotřebuje kontroly null. Přidání atributu informuje statickou analýzu kompilátoru, která IsNullOrEmpty
provádí nezbytnou kontrolu null: když se vrátí false
, argument není null
.
string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.
Metoda String.IsNullOrEmpty(String) bude anotována, jak je znázorněno výše pro .NET Core 3.0. V základu kódu můžete mít podobné metody, které kontrolují stav objektů pro hodnoty null. Kompilátor nerozpozná vlastní metody kontroly null a budete muset přidat poznámky sami. Když přidáte atribut, statická analýza kompilátoru ví, kdy testovaná proměnná byla zkontrolována hodnotou null.
Dalším použitím těchto atributů je Try*
vzor. Postconditions for ref
and out
arguments are communicated through the return value. Zvažte tuto metodu uvedenou dříve (v zakázaném kontextu s možnou hodnotou null):
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
Předchozí metoda se řídí typickým idiomem .NET: návratová hodnota označuje, zda message
byla nastavena na nalezenou hodnotu, nebo pokud nebyla nalezena žádná zpráva, na výchozí hodnotu. Pokud metoda vrátí true
hodnotu , hodnota message
není null; jinak metoda nastaví message
na hodnotu null.
V kontextu s povolenou hodnotou null můžete informovat tento idiom pomocí atributu NotNullWhen
. Při přidávání poznámek k parametrům pro odkazové typy s možnou hodnotou null vytvořte message
string?
atribut a přidejte ho:
bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message is not null;
}
V předchozím příkladu je známo, že hodnota message
není null při TryGetMessage
vrácení true
. Podobné metody byste měli ve svém základu kódu komentovat stejným způsobem: argumenty by mohly být stejné null
a jsou známé, že nemají hodnotu null, když metoda vrátí true
.
Možná budete potřebovat i jeden konečný atribut. Někdy stav null návratové hodnoty závisí na stavu null jednoho nebo více argumentů. Tyto metody vrátí nenulovou hodnotu vždy, když některé argumenty nejsou null
. Chcete-li správně anotovat tyto metody, použijte NotNullIfNotNull
atribut. Zvažte následující metodu:
string GetTopLevelDomainFromFullUrl(string url)
url
Pokud argument nemá hodnotu null, výstup není null
. Po povolení odkazů s možnou hodnotou null je potřeba přidat další poznámky, pokud vaše rozhraní API může přijmout argument null. Návratový typ můžete komentovat, jak je znázorněno v následujícím kódu:
string? GetTopLevelDomainFromFullUrl(string? url)
To také funguje, ale často vynutí volající implementaci dodatečných null
kontrol. Kontrakt je, že vrácená hodnota by byla null
pouze tehdy, když je null
argument .url
Chcete-li vyjádřit tento kontrakt, můžete tuto metodu komentovat, jak je znázorněno v následujícím kódu:
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
Předchozí příklad používá nameof
operátor pro parametr url
. Tato funkce je dostupná v jazyce C# 11. Před C# 11 budete muset zadat název parametru jako řetězec. Vrácená hodnota a argument byly opatřeny poznámkami ?
s indikací, že je možné null
použít jednu z těchto hodnot . Atribut dále objasňuje, že návratová hodnota nebude null, pokud url
argument není null
.
Podmíněné postconditions zadáte pomocí těchto atributů:
- MožnáNullWhen: Nenulable argument může být null, když metoda vrátí zadanou
bool
hodnotu. - NotNullWhen: Argument s možnou hodnotou null nebude null, pokud metoda vrátí zadanou
bool
hodnotu. - NotNullIfNotNull: Návratová hodnota není null, pokud argument pro zadaný parametr nemá hodnotu null.
Pomocné metody: MemberNotNull
a MemberNotNullWhen
Tyto atributy určují váš záměr, když refaktorujete běžný kód z konstruktorů do pomocných metod. Kompilátor jazyka C# analyzuje konstruktory a inicializátory polí, aby se zajistilo, že jsou před vrácením každého konstruktoru inicializována všechna referenční pole bez hodnoty null. Kompilátor jazyka C# ale nesleduje přiřazení polí všemi pomocnými metodami. Kompilátor vydává upozornění CS8618
, když pole nejsou inicializována přímo v konstruktoru, ale spíše v pomocné metodě. Do deklarace metody přidáte MemberNotNullAttribute deklaraci metody a určíte pole, která jsou inicializována na hodnotu, která není null v metodě. Představte si například následující příklad:
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();
}
}
Jako argumenty konstruktoru atributu MemberNotNull
můžete zadat více názvů polí.
Argument MemberNotNullWhenAttribute má bool
. Používáte MemberNotNullWhen
v situacích, kdy pomocná metoda vrací bool
inicializovaná pole pomocné metody.
Zastavení analýzy s možnou hodnotou null při vyvolání metody
Některé metody, obvykle pomocné rutiny výjimek nebo jiné pomocné metody, vždy ukončete vyvoláním výjimky. Nebo může pomocná rutina vyvolat výjimku na základě hodnoty logického argumentu.
V prvním případě můžete přidat DoesNotReturnAttribute atribut do deklarace metody. Analýza stavu null kompilátoru nekontroluje žádný kód v metodě, která následuje volání metody anotované pomocí DoesNotReturn
. Zvažte tuto metodu:
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}
public void SetState(object containedField)
{
if (containedField is null)
{
FailFast();
}
// containedField can't be null:
_field = containedField;
}
Kompilátor po volání FailFast
nedává žádná upozornění .
V druhém případě přidáte System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute atribut do logického parametru metody. Předchozí příklad můžete upravit následujícím způsobem:
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;
}
Pokud hodnota argumentu odpovídá hodnotě DoesNotReturnIf
konstruktoru, kompilátor po této metodě neprovádí žádnou analýzu stavu null.
Shrnutí
Přidání referenčních typů s možnou hodnotou null poskytuje počáteční slovník pro popis očekávání rozhraní API pro proměnné, které by mohly být null
. Atributy poskytují bohatší slovník, který popisuje stav null proměnných jako předpoklady a postconditions. Tyto atributy jasně popisují vaše očekávání a poskytují vývojářům lepší prostředí, které používají vaše rozhraní API.
Když aktualizujete knihovny pro kontext s možnou hodnotou null, přidejte tyto atributy, které uživatele vašich rozhraní API povedou ke správnému použití. Tyto atributy vám pomůžou plně popsat stav null argumentů a návratových hodnot.
- AllowNull: Pole, parametr nebo vlastnost, které nelze null použít, může mít hodnotu null.
- DisallowNull: Pole, parametr nebo vlastnost s možnou hodnotou null by nikdy neměly být null.
- MožnáNull: Pole, parametr, vlastnost nebo návratová hodnota, která není null, může být null.
- NotNull: Pole s možnou hodnotou null, parametr, vlastnost nebo návratová hodnota nikdy nebude null.
- MožnáNullWhen: Nenulable argument může být null, když metoda vrátí zadanou
bool
hodnotu. - NotNullWhen: Argument s možnou hodnotou null nebude null, pokud metoda vrátí zadanou
bool
hodnotu. - NotNullIfNotNull: Parametr, vlastnost nebo návratová hodnota není null, pokud argument pro zadaný parametr není null.
- DoesNotReturn: Metoda nebo vlastnost nikdy nevrací. Jinými slovy, vždy vyvolá výjimku.
- DoesNotReturnIf: Tato metoda nebo vlastnost nikdy nevrací, pokud přidružený
bool
parametr má zadanou hodnotu.