Megosztás a következőn keresztül:


A C#-fordító által értelmezett nullállapot-statikus elemzés attribútumai

Null értékű környezetben a fordító statikus kódelemzést végez az összes referenciatípus-változó nullállapotának meghatározásához:

  • not-null: A statikus elemzés azt határozza meg, hogy egy változó nem null értékű-e.
  • maybe-null: A statikus elemzés nem tudja megállapítani, hogy egy változó nem null értékű-e.

Ezek az állapotok lehetővé teszik a fordító számára, hogy figyelmeztetéseket adjon meg, amikor null értéket halaszthat el, és egy System.NullReferenceException. Ezek az attribútumok szemantikai információkat nyújtanak a fordítónak az argumentumok nullállapotáról , az értékek visszaadásáról és az objektumtagokról az argumentumok állapota és a visszatérési értékek alapján. A fordító pontosabb figyelmeztetéseket biztosít, ha az API-kat megfelelően jegyzetelték meg ezekkel a szemantikai információkkal.

Ez a cikk röviden ismerteti az egyes null értékű hivatkozástípus-attribútumokat és azok használatát.

Kezdjük egy példával. Tegyük fel, hogy a kódtár az alábbi API-val rendelkezik egy erőforrás-sztring lekéréséhez. Ezt a metódust eredetileg null értékű, oblivious környezetben állították össze:

bool TryGetMessage(string key, out string message)
{
    if (_messageMap.ContainsKey(key))
        message = _messageMap[key];
    else
        message = null;
    return message != null;
}

Az előző példa a .NET ismerős Try* mintáját követi. Ennek az API-nak két referenciaparamétere van: az key és a message. Ennek az API-nak a következő szabályai vannak a paraméterek nullállapotára vonatkozóan:

  • A hívók nem adhatják meg null argumentumként a következőt key: .
  • A hívók olyan változót adhatnak át, null amelynek értéke argumentumként szerepel message.
  • Ha a TryGetMessage metódus eredményül ad truevissza, a metódus értéke message nem null. Ha a visszatérési message érték false, null értékű.

A szabály key tömören kifejezhető: key nem null értékű referenciatípusnak kell lennie. A message paraméter összetettebb. Lehetővé teszi egy argumentumként megadott null változót, de garantálja, hogy az out argumentum nem nullaz . Ezekben a forgatókönyvekben gazdagabb szókincsre van szükség az elvárások leírásához. Az NotNullWhen alább ismertetett attribútum a paraméterhez használt argumentum nullállapotátmessage ismerteti.

Feljegyzés

Az attribútumok hozzáadása további információkat nyújt a fordítónak az API szabályairól. Ha a hívó kódot null értékű, engedélyezett környezetben fordítják le, a fordító figyelmezteti a hívókat, ha megsértik ezeket a szabályokat. Ezek az attribútumok nem teszik lehetővé a megvalósítás további ellenőrzését.

Attribútum Kategória Értelmezés
AllowNull Előfeltétel Egy nem null értékű paraméter, mező vagy tulajdonság null értékű lehet.
DisallowNull Előfeltétel A null értékű paraméter, mező vagy tulajdonság soha nem lehet null értékű.
MaybeNull Utókondíció Nem null értékű paraméter, mező, tulajdonság vagy visszatérési érték lehet null.
NotNull Utókondíció A null értékű paraméter, mező, tulajdonság vagy visszatérési érték soha nem lesz null.
MaybeNullWhen Feltételes utókondíció A nem null értékű argumentum null értékű lehet, ha a metódus a megadott bool értéket adja vissza.
NotNullWhen Feltételes utókondíció A null értékű argumentumok nem lesznek null értékűek, ha a metódus a megadott bool értéket adja vissza.
NotNullIfNotNull Feltételes utókondíció A visszaadott érték, tulajdonság vagy argumentum nem null értékű, ha a megadott paraméter argumentuma nem null.
MemberNotNull Metódus- és tulajdonságsegítő módszerek A listában szereplő tag nem lesz null értékű, amikor a metódus visszatér.
MemberNotNullWhen Metódus- és tulajdonságsegítő módszerek A listában szereplő tag nem lesz null értékű, ha a metódus a megadott bool értéket adja vissza.
DoesNotReturn Nem elérhető kód Egy metódus vagy tulajdonság soha nem ad vissza. Más szóval, ez mindig kivételt vet ki.
DoesNotReturnIf Nem elérhető kód Ez a metódus vagy tulajdonság soha nem ad vissza értéket, ha a társított bool paraméter a megadott értékkel rendelkezik.

Az előző leírások rövid áttekintést adnak arról, hogy az egyes attribútumok mit is végeznek. Az alábbi szakaszok részletesebben ismertetik ezeknek az attribútumoknak a viselkedését és jelentését.

Előfeltételek: AllowNull és DisallowNull

Fontolja meg egy olyan olvasási/írási null tulajdonságot, amely soha nem ad vissza, mert ésszerű alapértelmezett értékkel rendelkezik. A hívók az alapértelmezett értékre való beállításkor adják át null a hívóknak a beállított tartozékot. Vegyük például azt az üzenetküldő rendszert, amely egy csevegőszobában kéri a képernyő nevét. Ha nincs megadva, a rendszer véletlenszerű nevet hoz létre:

public string ScreenName
{
    get => _screenName;
    set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;

Ha az előző kódot null értékű, oblivious környezetben fordítja le, minden rendben van. A null értékű hivatkozástípusok engedélyezése után a ScreenName tulajdonság nem null értékű hivatkozássá válik. Ez helyes a get tartozékhoz: soha nem ad nullvissza . A hívóknak nem kell ellenőrizni a visszaadott tulajdonságot null. Most azonban a tulajdonság null beállítása figyelmeztetést eredményez. Az ilyen típusú kód támogatásához adja hozzá az System.Diagnostics.CodeAnalysis.AllowNullAttribute attribútumot a tulajdonsághoz az alábbi kódban látható módon:

[AllowNull]
public string ScreenName
{
    get => _screenName;
    set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();

Előfordulhat, hogy hozzá kell adnia egy using irányelvet System.Diagnostics.CodeAnalysis ennek és a cikkben tárgyalt egyéb attribútumoknak a használatához. Az attribútum nem a tartozékra, hanem a tulajdonságra van alkalmazva set . Az AllowNull attribútum előfeltételeket határoz meg, és csak az argumentumokra vonatkozik. A get tartozék visszatérési értékkel rendelkezik, de nincsenek paraméterek. Ezért az AllowNull attribútum csak a set tartozékra vonatkozik.

Az előző példa bemutatja, mit kell keresni az AllowNull attribútum argumentumhoz való hozzáadásakor:

  1. A változó általános szerződése, hogy nem lehet null, ezért nem null értékű referenciatípust szeretne.
  2. Vannak olyan forgatókönyvek, amikor a hívó argumentumként adja át null a hívót, bár nem ez a leggyakoribb használat.

Ez az attribútum leggyakrabban tulajdonságokhoz vagy in, outés ref argumentumokhoz szükséges. Az AllowNull attribútum a legjobb választás, ha egy változó általában nem null értékű, de előfeltételként engedélyeznie null kell.

Ellentétben a következő forgatókönyvekkel DisallowNull: Ezzel az attribútummal megadhatja, hogy egy null értékű hivatkozástípus argumentuma ne legyen null. Fontolja meg az alapértelmezett értéknek számító tulajdonságot null , de az ügyfelek csak nem null értékűre állíthatják be. Tekintse meg az alábbi kódot:

public string ReviewComment
{
    get => _comment;
    set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;

Az előző kód a legjobb módja annak, hogy kifejezze a tervét, amely ReviewComment lehet null, de nem állítható be null. Ha ez a kód null értékű, a következővel egyértelműbbé teheti ezt a fogalmat a System.Diagnostics.CodeAnalysis.DisallowNullAttributehívóknak:

[DisallowNull]
public string? ReviewComment
{
    get => _comment;
    set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;

Null értékű környezetben a ReviewCommentget tartozék visszaadhatja az alapértelmezett értéket null. A fordító figyelmezteti, hogy a hozzáférés előtt ellenőrizni kell. Ezenkívül figyelmezteti a hívókat, hogy bár lehetséges null, a hívónak nem szabad explicit módon beállítania null. Az DisallowNull attribútum egy előfeltételt is megad, nem befolyásolja a tartozékotget. Az attribútumot akkor DisallowNull használja, amikor az alábbi jellemzőket figyeli meg:

  1. A változó alapforgatókönyvekben is szerepelhet null , gyakran az első példányosításkor.
  2. A változót nem szabad explicit módon beállítani null.

Ezek a helyzetek gyakoriak az eredetileg null értékű kódban. Előfordulhat, hogy az objektumtulajdonságok két különböző inicializálási műveletben vannak beállítva. Előfordulhat, hogy egyes tulajdonságok csak az aszinkron munka befejezése után vannak beállítva.

Az AllowNull és DisallowNull az attribútumok lehetővé teszik annak megadását, hogy a változók előfeltételei nem feltétlenül egyeznek a változók null értékű széljegyzetével. Ezek részletesebben ismertetik az API jellemzőit. Ez a további információ segít a hívóknak az API helyes használatát. Ne feledje, hogy az alábbi attribútumokkal határoz meg előfeltételeket:

  • AllowNull: Egy nem null értékű argumentum lehet null.
  • DisallowNull: A null értékű argumentumnak soha nem szabad null értékűnek lennie.

Utókondíciók: MaybeNull és NotNull

Tegyük fel, hogy rendelkezik a következő aláírással rendelkező metódussal:

public Customer FindCustomer(string lastName, string firstName)

Valószínűleg írt egy ilyen metódust, amely akkor tér vissza null , ha a keresett név nem található. Az null egyértelműen jelzi, hogy a rekord nem található. Ebben a példában valószínűleg a visszatérési típust Customer a következőre Customer?módosítja: . A visszatérési érték null értékű hivatkozástípusként való deklarálása egyértelműen meghatározza az API szándékát:

public Customer? FindCustomer(string lastName, string firstName)

A Generics nullabilitása miatt előfordulhat, hogy ez a technika nem hozza létre az API-nak megfelelő statikus elemzést. Előfordulhat, hogy egy hasonló mintát követő általános metódussal rendelkezik:

public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)

A metódus akkor ad vissza, null ha a keresett elem nem található. Egyértelművé teheti, hogy a metódus akkor ad vissza értéket null , ha egy elem nem található, ha hozzáadja a MaybeNull megjegyzést a metódus visszatéréséhez:

[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)

Az előző kód tájékoztatja a hívókat, hogy a visszatérési érték valójában null lehet . Arról is tájékoztatja a fordítót, hogy a metódus akkor is visszaadhat egy null kifejezést, ha a típus nem null értékű. Ha olyan általános metódussal rendelkezik, amely a típusparaméter egy példányát adja vissza, kifejezheti, Thogy az attribútum használatával NotNull soha nem tér visszanull.

Azt is megadhatja, hogy egy visszaadott érték vagy argumentum ne legyen null értékű, annak ellenére, hogy a típus null értékű hivatkozástípus. Az alábbi módszer egy segédmetódus, amely akkor dob, ha az első argumentuma a következő null:

public static void ThrowWhenNull(object value, string valueExpression = "")
{
    if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}

Ezt a rutint a következőképpen hívhatja meg:

public static void LogMessage(string? message)
{
    ThrowWhenNull(message, $"{nameof(message)} must not be null");

    Console.WriteLine(message.Length);
}

A nullhivatkozás-típusok engedélyezése után meg kell győződnie arról, hogy az előző kód figyelmeztetések nélkül fordítható le. A metódus visszatérése esetén a value paraméter garantáltan nem null értékű. Elfogadható azonban null hivatkozással meghívni ThrowWhenNull . value Létrehozhat egy null értékű hivatkozástípust, és hozzáadhatja az NotNull utólagos feltételt a paraméterdeklarációhoz:

public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
    _ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
    // other logic elided

Az előző kód egyértelműen kifejezi a meglévő szerződést: A hívók átadhatnak egy változót az null értékkel, de az argumentum garantáltan soha nem lesz null, ha a metódus kivétel nélkül tér vissza.

Feltétel nélküli utókondíciókat a következő attribútumokkal adhat meg:

  • MaybeNull: A nem null értékű visszatérési érték null értékű lehet.
  • NotNull: A null értékű visszatérési érték soha nem lesz null.

Feltételes utófeltételek: NotNullWhen, MaybeNullWhenés NotNullIfNotNull

Valószínűleg ismeri a metódust stringString.IsNullOrEmpty(String). Ez a metódus null értékű vagy üres sztring esetén ad vissza true értéket. Ez a null-ellenőrzés egyik formája: A hívóknak nem kell null-ellenőrzéssel ellenőrizni az argumentumot false, ha a metódus visszatér. Ha egy ilyen null értékű metódust szeretne felismerni, állítsa az argumentumot null értékű hivatkozástípusra, és adja hozzá az NotNullWhen attribútumot:

bool IsNullOrEmpty([NotNullWhen(false)] string? value)

Ez tájékoztatja a fordítót arról, hogy a visszatérési értéket false tartalmazó kódoknak nincs szükségük null értékű ellenőrzésekre. Az attribútum hozzáadása tájékoztatja a fordító statikus elemzését, amely IsNullOrEmpty elvégzi a szükséges null ellenőrzést: amikor visszaadja false, az argumentum nem null.

string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
    int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.

A String.IsNullOrEmpty(String) metódus a .NET Core 3.0-hoz fent látható módon lesz jegyzetekkel elfűzve. Előfordulhat, hogy a kódbázisban hasonló metódusok ellenőrzik az objektumok állapotát null értékek esetén. A fordító nem ismeri fel az egyéni nullellenőrzési módszereket, és önnek kell hozzáadnia a széljegyzeteket. Az attribútum hozzáadásakor a fordító statikus elemzése tudja, hogy a tesztelt változó mikor lett null értékű.

Ezeknek az attribútumoknak egy másik felhasználási módja a Try* minta. A rendszer a visszatérési értéken keresztül közli a postconditions és refout az argumentumokat. Fontolja meg ezt a korábban bemutatott módszert (null értékű letiltott környezetben):

bool TryGetMessage(string key, out string message)
{
    if (_messageMap.ContainsKey(key))
        message = _messageMap[key];
    else
        message = null;
    return message != null;
}

Az előző metódus egy tipikus .NET-idiómát követ: a visszatérési érték azt jelzi, hogy message a rendszer a talált értékre vagy , ha nem található üzenet, az alapértelmezett értékre van-e beállítva. Ha a metódus eredményül ad truevissza, a metódus értéke message nem null, ellenkező esetben a metódus null értékre van adva message .

Null értékű, engedélyezett környezetben az attribútum használatával kommunikálhatja ezt az NotNullWhen idiómát. Ha a null értékű hivatkozástípusok paramétereit jegyzeteli, hozzon létre message és string? adjon hozzá egy attribútumot:

bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
    if (_messageMap.ContainsKey(key))
        message = _messageMap[key];
    else
        message = null;
    return message is not null;
}

Az előző példában a visszaadott érték message nem null TryGetMessagetrueértékű. Hasonló metódusokat ugyanúgy kell jegyzetelni a kódbázisban: az argumentumok egyenlők nulllehetnek, és a metódus visszatérésekor truenem null értékűek.

Egy végső attribútumra is szükség lehet. Előfordulhat, hogy egy visszatérési érték null állapota egy vagy több argumentum null állapotától függ. Ezek a metódusok nem null értéket adnak vissza, ha bizonyos argumentumok nem null. A metódusok helyes megjegyzéséhez használja az NotNullIfNotNull attribútumot. Fontolja meg a következő módszert:

string GetTopLevelDomainFromFullUrl(string url)

Ha az url argumentum nem null értékű, a kimenet nem null. A null értékű hivatkozások engedélyezése után további széljegyzeteket kell hozzáadnia, ha az API elfogadhat null argumentumot. A visszatérési típust a következő kódban látható módon fűzheti megjegyzéshez:

string? GetTopLevelDomainFromFullUrl(string? url)

Ez is működik, de gyakran kényszeríti a hívókat további null ellenőrzések végrehajtására. A szerződés az, hogy a visszatérési érték csak akkor lesznull, ha az argumentum url .null A szerződés kifejezéséhez a következő kódban látható módon kell megjegyzést fűznie ehhez a módszerhez:

[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)

Az előző példa az operátort nameof használja a paraméterhez url. Ez a funkció a C# 11-ben érhető el. A C# 11 előtt sztringként kell beírnia a paraméter nevét. A visszatérési érték és az argumentum is széljegyzetet kapott azzal a ? jelzéssel, hogy bármelyik lehet null. Az attribútum azt is egyértelművé teszi, hogy a visszatérési érték nem lesz null értékű, ha az url argumentum nem null.

A feltételes utókondíciókat az alábbi attribútumokkal adhatja meg:

  • MaybeNullWhen: A nem null értékű argumentum null értékű lehet, ha a metódus a megadott bool értéket adja vissza.
  • NotNullWhen: A null értékű argumentum nem lesz null értékű, ha a metódus a megadott bool értéket adja vissza.
  • NotNullIfNotNull: A visszatérési érték nem null, ha a megadott paraméter argumentuma nem null.

Segítő módszerek: MemberNotNull és MemberNotNullWhen

Ezek az attribútumok határozzák meg a szándékot, amikor a konstruktorokból átszerkesztette a közös kódot segédmeteként. A C#-fordító elemzi a konstruktorokat és a mező inicializálóit, hogy minden nem null értékű referenciamező inicializálva legyen az egyes konstruktorok visszatérése előtt. A C#-fordító azonban nem követi nyomon a mezőhozzárendeléseket az összes segédmeteképpen. A fordító figyelmeztetést CS8618 ad ki, ha a mezőket nem közvetlenül a konstruktorban inicializálják, hanem segédmetódusban. Adja hozzá a MemberNotNullAttribute metódusdeklarációhoz, és adja meg a metódus nem null értékére inicializált mezőket. Vegyük például a következő példát:

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();
    }
}

Az attribútumkonstruktor MemberNotNull argumentumaként több mezőnevet is megadhat.

Ennek MemberNotNullWhenAttribute argumentuma van bool . Olyan helyzetekben használja MemberNotNullWhen , amikor a segédmetódus azt bool jelzi, hogy a segédmetódus inicializált-e mezőket.

Null értékű elemzés leállítása metódusdobáskor

Egyes metódusok, jellemzően kivételsegítők vagy más segédprogramok kivétellel mindig kilépnek. Vagy egy segítő kivételt okozhat egy logikai argumentum értéke alapján.

Az első esetben hozzáadhatja az attribútumot a DoesNotReturnAttribute metódusdeklarációhoz. A fordító nullállapot-elemzése nem ellenőrzi a metódus olyan kódját, amely egy olyan metódus meghívását követi, amely a következővel DoesNotReturnvan eljegyzve: . Fontolja meg ezt a módszert:

[DoesNotReturn]
private void FailFast()
{
    throw new InvalidOperationException();
}

public void SetState(object containedField)
{
    if (containedField is null)
    {
        FailFast();
    }

    // containedField can't be null:
    _field = containedField;
}

A fordító nem ad ki figyelmeztetést a hívás FailFastután.

A második esetben hozzáadja az attribútumot a System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute metódus logikai paraméteréhez. Az előző példát az alábbiak szerint módosíthatja:

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;
}

Ha az argumentum értéke megegyezik a DoesNotReturnIf konstruktor értékével, a fordító nem végez nullállapot-elemzést a metódus után.

Összegzés

Null értékű referenciatípusok hozzáadása kezdeti szókészletet biztosít az API-k változókkal nullkapcsolatos elvárásainak leírásához. Az attribútumok gazdagabb szókészletet biztosítanak a változók null állapotának előfeltételként és utókondícióként való leírásához. Ezek az attribútumok jobban leírják az elvárásait, és jobb élményt nyújtanak az API-kat használó fejlesztők számára.

Amikor null értékű környezet kódtárait frissíti, adja hozzá ezeket az attribútumokat, hogy az API-k felhasználói a megfelelő használathoz vezessenek. Ezek az attribútumok segítenek az argumentumok null állapotának teljes leírásában és az értékek visszaadásában.

  • AllowNull: Egy nem null értékű mező, paraméter vagy tulajdonság null értékű lehet.
  • DisallowNull: A null értékű mező, paraméter vagy tulajdonság soha nem lehet null értékű.
  • MaybeNull: Lehet, hogy egy nem null értékű mező, paraméter, tulajdonság vagy visszatérési érték null értékű.
  • NotNull: A null értékű mező, paraméter, tulajdonság vagy visszatérési érték soha nem lesz null.
  • MaybeNullWhen: A nem null értékű argumentum null értékű lehet, ha a metódus a megadott bool értéket adja vissza.
  • NotNullWhen: A null értékű argumentum nem lesz null értékű, ha a metódus a megadott bool értéket adja vissza.
  • NotNullIfNotNull: A paraméter, tulajdonság vagy visszatérési érték nem null, ha a megadott paraméter argumentuma nem null.
  • DoesNotReturn: Egy metódus vagy tulajdonság soha nem ad vissza. Más szóval, ez mindig kivételt vet ki.
  • DoesNotReturnIf: Ez a metódus vagy tulajdonság soha nem ad vissza értéket, ha a társított bool paraméter a megadott értékkel rendelkezik.