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 pro key.
  • Volající mohou předat proměnnou, jejíž hodnota je null jako argument pro message.
  • Pokud metoda TryGetMessage vrátí true, hodnota message není null. Pokud je falsevrácená hodnota , hodnota message 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 :

  1. Obecný kontrakt pro tuto proměnnou je, že by neměl být null, takže chcete typ odkazu bez hodnoty null.
  2. 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 inout, 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:

  1. Proměnná může být null v základních scénářích, často při první instanci.
  2. 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, Tmůž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, MaybeNullWhena 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í truehodnotu , 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é nulla 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 nullargument .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é nullpouží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 MemberNotNullWhenAttributebool . 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í FailFastnedá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.