Atributy pro statickou analýzu stavu s hodnotou null
V kontextu s povolenou hodnotou null provede kompilátor statickou analýzu kódu k určení hodnoty null všech proměnných referenčního typu:
- NOT-NULL: Statická analýza určuje, že proměnná obsahuje hodnotu, která není null.
- možná-null: Statická analýza nemůže určit, že má proměnná přiřazenou jinou hodnotu než null.
Tyto stavy umožňují kompilátoru poskytnout upozornění, když můžete odkázat na hodnotu null, vyvolání System.NullReferenceException . Tyto atributy poskytují kompilátor se sémantickými informacemi o stavu argumentu null argumentů, vrácenými hodnotami a členy objektu na základě stavu argumentů a vrácených hodnot. Kompilátor poskytuje přesnější upozornění, když jsou rozhraní API správně opatřena poznámkami s těmito sémantickými informacemi.
Tento článek poskytuje stručný popis jednotlivých atributů typu odkazu s možnou hodnotou null a jejich použití.
Pojďme začít s příkladem. Imagine vaše knihovna obsahuje následující rozhraní API k načtení řetězce prostředku. Tato metoda byla původně zapsána před poznámkami C# 8,0 a 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 rozhraní .NET. Pro toto rozhraní API existují dva referenční parametry: key a message . Toto rozhraní API má následující pravidla týkající se stavu hodnoty null těchto parametrů:
- Volající by neměli předat
nulljako argument prokey. - Volající mohou předat proměnnou, jejíž hodnota je
nulljako argument promessage. - Pokud
TryGetMessageMetoda vrátítruehodnotu, hodnotamessagenení null. Pokud vrácená hodnota jefalse,hodnota, kterámessageje null.
Pravidlo pro key lze vyjádřit stručně v jazyce C# 8,0: key by měl být odkazový typ, který nelze nastavit na hodnotu null. messageParametr je složitější. Umožňuje proměnnou, která je null jako argument, ale garantuje, že out argument není null . V těchto scénářích potřebujete podrobnější slovník, který popisuje očekávání. NotNullWhenAtribut popsaný níže popisuje stav null pro argument použitý pro message parametr.
Poznámka
Přidáním těchto atributů poskytne kompilátor více informací o pravidlech pro vaše rozhraní API. Při volání kódu v kontextu s povolenou hodnotou null, kompilátor upozorní volající, když porušují tato pravidla. Tyto atributy neumožňují více kontrol implementace.
| Atribut | Kategorie | Význam |
|---|---|---|
| AllowNull má | Předběžná podmínka | Parametr, pole nebo vlastnost, která nesmí mít hodnotu null, může mít hodnotu null. |
| DisallowNull | Předběžná podmínka | Parametr s možnou hodnotou null, pole nebo vlastnost by nikdy neměly mít hodnotu null. |
| MaybeNull | Následná podmínka | Parametr, který nemůže mít hodnotu null, pole, vlastnost nebo návratová hodnota může mít hodnotu null. |
| NotNull | Následná podmínka | Parametr s možnou hodnotou null, pole, vlastnost nebo návratová hodnota nikdy nebude null. |
| MaybeNullWhen | Podmíněný následná podmínka | Argument, který nemůže mít hodnotu null, může mít hodnotu null, pokud metoda vrátí zadanou bool hodnotu. |
| NotNullWhen | Podmíněný následná podmínka | Argument s možnou hodnotou null nebude mít hodnotu null, pokud metoda vrátí zadanou bool hodnotu. |
| NotNullIfNotNull | Podmíněný následná podmínka | Návratová hodnota, vlastnost nebo argument nemá hodnotu null, pokud argument pro zadaný parametr není null. |
| MemberNotNull | Pomocné metody metod a vlastností | Pokud metoda vrátí uvedený člen, nebude mít hodnotu null. |
| MemberNotNullWhen | Pomocné metody metod a vlastností | Uvedený člen nebude mít hodnotu null, pokud metoda vrátí zadanou bool hodnotu. |
| DoesNotReturn | Nedosažitelný kód | Metoda nebo vlastnost se nikdy nevrátí. Jinými slovy, vždy vyvolá výjimku. |
| DoesNotReturnIf | Nedosažitelný kód | Tato metoda nebo vlastnost se nikdy nevrátí, pokud bool má přidružený parametr zadanou hodnotu. |
Předchozí popisy představují rychlý odkaz na to, co každý atribut dělá. V následujících částech jsou popsány chování a význam těchto atributů.
Předběžné podmínky: 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í přecházejí null do přístupového objektu set při jeho nastavení na tuto výchozí hodnotu. Představte si třeba systém zasílání zpráv, který se zeptá na název obrazovky v chatovací místnosti. Pokud není k dispozici žádný, systém vygeneruje náhodný název:
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;
Pokud kompilujete předchozí kód v oblivious kontextu s možnou hodnotou null, vše je dobré. Po povolení typů odkazů s možnou hodnotou null se ScreenName vlastnost zobrazí jako odkaz bez hodnoty null. To je správné pro get přistupující objekt: nikdy se nevrátí null . Volající nepotřebují kontrolovat vrácenou vlastnost pro null . Ale teď vlastnost nastavíme tak, aby null vygenerovala upozornění. Pro podporu tohoto typu 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();
usingPro System.Diagnostics.CodeAnalysis použití tohoto a dalších atributů, které jsou popsány v tomto článku, bude pravděpodobně nutné přidat direktivu. Atribut se aplikuje na vlastnost, nikoli na set přistupující objekt. AllowNullAtribut určuje předběžné podmínky a vztahuje se pouze na argumenty. getPřístupový objekt má návratovou hodnotu, ale nemá žádné parametry. Proto se AllowNull atribut vztahuje pouze na set přístup.
Předchozí příklad ukazuje, co je třeba najít při přidávání AllowNull atributu v argumentu:
- Obecnou smlouvou pro tuto proměnnou je, že by neměla být
null, takže chcete typ odkazu, který nepovoluje hodnotu null. - Existují scénáře, které má volající předat
nulljako argument, i když nejsou nejběžnějším využitím.
Nejčastěji budete potřebovat tento atribut pro vlastnosti, nebo in out a ref argumenty. AllowNullAtribut je nejlepší volbou v případě, že proměnná obvykle není null, ale je nutné ji null použít jako předběžnou podmínku.
Rozdíl ve scénářích použití DisallowNull : pomocí tohoto atributu určíte, že argument typu odkazu s možnou hodnotou null by neměl být null . Vezměte v úvahu vlastnost null , kde je výchozí hodnota, ale klienti ji můžou nastavit jenom na jinou hodnotu než null. Vezměme si 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ším způsobem, jak vyjádřit návrh, který ReviewComment může být null , ale nelze jej nastavit na null . Jakmile tento kód bude mít na paměti hodnotu s možnou hodnotou null, můžete tento koncept srozumitelně 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 hodnotou null ReviewComment get může přístupový objekt vrátit výchozí hodnotu null . Kompilátor upozorní, že před přístupem musí být zkontrolován. Kromě toho upozorňuje volající, že i když to může být null , volající by se neměli explicitně nastavit na null . DisallowNullAtribut také určuje předběžnou podmínku, nemá vliv na get přistupující objekt. Použijete DisallowNull -li tyto vlastnosti, použijte atribut:
- Tato proměnná může být
nullv základních scénářích, často při první instanci. - Proměnná by neměla být explicitně nastavena na
null.
Tyto situace jsou běžné v kódu, který byl původně nulový nevěnný. Je možné, že vlastnosti objektu jsou nastaveny ve dvou odlišných inicializačních operacích. Může se stát, že některé vlastnosti se nastaví až po dokončení nějaké asynchronní práce.
Atributy a umožňují určit, že předběžné podmínky proměnných nemusí odpovídat anotacím s možnou hodnotou AllowNull DisallowNull null u těchto proměnných. Ty poskytují další podrobnosti o charakteristikách vašeho rozhraní API. Tyto další informace pomáhají volajícím správně používat vaše rozhraní API. Nezapomeňte zadat předběžné podmínky pomocí následujících atributů:
- AllowNull:Argument, který nemůže mít hodnotu null, může mít hodnotu null.
- DisallowNull:Argument s možnou hodnotou null by nikdy neměl mít hodnotu null.
Postconditions (Podmínky po): 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 metodu, jako je tato, která se má vrátit, když nebyl nalezen null hledaný 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? . Deklarování návratové hodnoty jako typu odkazu s možnou hodnotou null jasně určuje záměr tohoto rozhraní API:
public Customer? FindCustomer(string lastName, string firstName)
Z důvodů uvedených v části Obecné typy s možnou hodnotou null nemusí tato technika vytvořit 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 se null vrátí, když hledaná položka není nalezena. Přidáním poznámky do návratu metody můžete objasnit, že metoda vrátí , když není null MaybeNull nalezena položka:
[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 mít ve skutečnosti hodnotu null. Informuje také kompilátor, že metoda může vrátit výraz, i když null typ není možné nullable. Pokud máte obecnou metodu, která vrací instanci parametru jejího typu , můžete vyjádřit, že se nikdy T nevrátí null pomocí NotNull atributu .
Můžete také určit, že návratová hodnota nebo argument není null, i když je typ odkazu s možnou hodnotou null. Následující metoda je pomocná metoda, která vyvolá , pokud je jejím prvním argumentem null :
public static void ThrowWhenNull(object value, string valueExpression = "")
{
if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}
Tuto rutinu můžete volat následujícím způsobem:
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 kompiluje bez upozornění. Po vrácení metody je value zaručeno, že parametr nebude mít hodnotu null. Je však přijatelné volat s ThrowWhenNull nulovým odkazem. Můžete vytvořit value odkazový typ s možnou hodnotou null a přidat do deklarace NotNull parametru podmínku post:
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 hodnotou, ale argumentu je zaručeno, že nikdy nebude mít hodnotu null, pokud se metoda vrátí bez null vyvolání výjimky.
Nepodmíněné podmínky zadáte pomocí následujících atributů:
- MaybeNull:Návratová hodnota, která nemusí mít hodnotu null, může být null.
- NotNull:Návratová hodnota s možnou hodnotou null nebude nikdy mít hodnotu null.
Podmíněné podmínky po: , a NotNullWhen MaybeNullWhen``NotNullIfNotNull
Pravděpodobně jste obeznámeni s metodou string String.IsNullOrEmpty(String) . Tato metoda vrátí true hodnotu null nebo prázdný řetězec. Jedná se o formu kontroly hodnoty null: Volající nemusí argument kontrolovat hodnotou null, pokud metoda vrátí false . Pokud chcete, aby metoda, jako je tato, s povolenou hodnotou null, nastavili byste argument na odkazový typ s možnou hodnotou null a přidejte NotNullWhen atribut :
bool IsNullOrEmpty([NotNullWhen(false)] string? value)
To informuje kompilátor, že jakýkoli kód, ve kterém je návratová hodnota, false nepotřebuje kontroly hodnoty null. Přidání atributu informuje statickou analýzu kompilátoru, která provádí nezbytnou kontrolu hodnoty null: když vrátí IsNullOrEmpty 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 kódu můžete mít podobné metody, které kontroluje stav objektů u hodnot null. Kompilátor nerozpozná vlastní metody kontroly null a poznámky budete muset přidat sami. Když přidáte atribut , statická analýza kompilátoru pozná, když byla otestovaná proměnná zaškrtnutá na hodnotu null.
Dalším použitím těchto atributů je Try* vzor. Argumenty postconditions pro a se ref out komunikují prostřednictvím návratové hodnoty. Zvažte tuto metodu zobrazenou 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, jestli byla nastavená na nalezenou hodnotu, nebo pokud se žádná zpráva message nenašla, na výchozí hodnotu. Pokud metoda vrátí hodnotu , hodnota není null. V opačném případě true se metoda nastaví na hodnotu message message null.
V povoleném kontextu s možnou hodnotou null můžete tento idiom sdělit pomocí NotNullWhen atributu . Při přidávání poznámek k parametrům pro odkazové typy s možnou hodnotou null vytvořte message a string? přidejte atribut:
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 není message null, když vrátí TryGetMessage true . Podobné metody byste měli v kódu anotovat stejným způsobem: argumenty by se mohly rovnat a je známo, že nejsou null, když null metoda vrátí true .
Můžete potřebovat také jeden konečný atribut. Stav hodnoty null návratové hodnoty někdy závisí na stavu null jednoho nebo více argumentů. Tyto metody vrátí hodnotu, která není null, kdykoli některé argumenty nejsou null . Ke správnému anotování těchto metod použijte NotNullIfNotNull atribut . Zvažte následující metodu:
string GetTopLevelDomainFromFullUrl(string url)
Pokud url argument není null, výstupem 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 anotovat, jak je znázorněno v následujícím kódu:
string? GetTopLevelDomainFromFullUrl(string? url)
To také funguje, ale často donutí volající implementovat dodatečné null kontroly. Kontrakt je, že návratová hodnota by byla null pouze v případě, že je argument url null . Pokud chcete tento kontrakt vyjádřit, opište tuto metodu poznámkami, jak je znázorněno v následujícím kódu:
[return: NotNullIfNotNull("url")]
string? GetTopLevelDomainFromFullUrl(string? url)
Vrácená hodnota i argument jsou anotovány pomocí indikující, ? že může být buď null . Atribut dále vysvětluje, že návratová hodnota nebude mít hodnotu null, pokud url argument není null .
Podmíněné podmínky můžete zadat pomocí těchto atributů:
- MaybeNullWhen:Argument, který nemůže mít hodnotu null, může mít hodnotu null, pokud metoda vrátí zadanou
boolhodnotu. - NotNullWhen: Argument s možnou hodnotou null nebude mít hodnotu null, pokud metoda vrátí zadanou
boolhodnotu. - NotNullIfNotNull:Návratová hodnota není null, pokud argument zadaného parametru není null.
Pomocné metody: MemberNotNull a MemberNotNullWhen
Tyto atributy určují váš záměr, když refaktorujte společný kód z konstruktorů do pomocných metod. Kompilátor jazyka C# analyzuje konstruktory a inicializátory polí, aby se ujistil, že všechna referenční pole s možnou hodnotou null byla inicializována před vrácením každého konstruktoru. Kompilátor jazyka C# ale nesleduje přiřazení polí prostřednictvím všech pomocých metod. Kompilátor vydává upozornění, pokud pole nejsou inicializována přímo v konstruktoru, ale spíše CS8618 v pomocné metodě. Přidáte do deklarace metody a určíte pole, která jsou inicializována na hodnotu, která není MemberNotNullAttribute 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 můžete zadat MemberNotNull více názvů polí.
Má MemberNotNullWhenAttribute bool argument . Použijete MemberNotNullWhen v situacích, kdy pomocná metoda vrátí indikující, jestli pole inicializovala bool pomocná metoda.
Zastavení analýzy s možnou hodnotou null při vyvolání vyvolání metody
Některé metody, obvykle pomocná volání výjimek nebo jiné pomocné metody, se vždy ukončí vyvoláním výjimky. Pomocná metoda také může vyvolat výjimku na základě hodnoty logického argumentu.
V prvním případě můžete přidat atribut DoesNotReturnAttribute do deklarace metody. Analýza stavu null kompilátoru neschová žádný kód v metodě, která následuje po volání metody s poznámkami 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í nevyvolá žádná FailFast upozornění.
V druhém případě přidáte atribut System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute 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ě konstruktoru, kompilátor po této metodě nebude provádět žádnou DoesNotReturnIf analýzu stavu null.
Souhrn
Důležité
Oficiální dokumentace sleduje nejnovější verzi v jazyce C#. V tuto chvíli píšete pro C# 9,0. V závislosti na verzi jazyka C#, kterou používáte, nemusí být k dispozici různé funkce. Výchozí verze C# pro váš projekt je založena na cílové verzi rozhraní .NET Framework. Další informace najdete v tématu výchozí hodnoty jazyka C#.
Přidání odkazových typů s možnou hodnotou null poskytuje počáteční slovník, který popisuje očekávání rozhraní API pro proměnné, které by mohly být null . Atributy poskytují bohatší slovník, který popisuje nulový stav proměnných jako předběžné a postpodmíněné podmínky. Tyto atributy jasněji popisují vaše očekávání a poskytují lepší prostředí pro vývojáře, kteří používají vaše rozhraní API.
Při aktualizaci knihoven pro kontext s možnou hodnotou null přidejte tyto atributy, které uživatele vašich rozhraní API navede na správné použití. Tyto atributy vám pomůžou plně popsat stav null argumentů a návratových hodnot.
- AllowNull:Pole, parametr nebo vlastnost, která nemůže mít hodnotu null, může mít hodnotu null.
- DisallowNull:Pole, parametr nebo vlastnost s možnou hodnotou null by nikdy neměly mít hodnotu null.
- MaybeNull:Pole, parametr, vlastnost nebo návratová hodnota, která nemůže mít hodnotu null, může mít hodnotu null.
- NotNull:Pole s možnou hodnotou null, parametr, vlastnost nebo návratová hodnota nikdy nebude mít hodnotu null.
- MaybeNullWhen: argument, který nemůže mít hodnotu null, může mít hodnotu null, pokud metoda vrátí zadanou
boolhodnotu. - NotNullWhen: argument s možnou hodnotou null nebude mít hodnotu null, pokud metoda vrátí zadanou
boolhodnotu. - NotNullIfNotNull: parametr, vlastnost nebo návratová hodnota nemají hodnotu null, pokud argument pro zadaný parametr není null.
- DoesNotReturn: metoda nebo vlastnost se nikdy nevrátí. Jinými slovy, vždy vyvolá výjimku.
- DoesNotReturnIf: Tato metoda nebo vlastnost se nikdy nevrátí, pokud
boolmá přidružený parametr zadanou hodnotu.