Attribut för statisk analys med null-tillstånd som tolkas av C#-kompilatorn
I en null-aktiverad kontext utför kompilatorn statisk analys av kod för att fastställa null-tillståndet för alla referenstypvariabler:
- not-null: Statisk analys avgör att en variabel har ett värde som inte är null.
- maybe-null: Statisk analys kan inte avgöra att en variabel tilldelas ett värde som inte är null.
Dessa tillstånd gör att kompilatorn kan ge varningar när du kan avreferera ett null-värde och utlösa ett System.NullReferenceException. Dessa attribut ger kompilatorn semantisk information om null-tillståndet för argument, returvärden och objektmedlemmar baserat på argumentens och returvärdenas tillstånd. Kompilatorn ger mer exakta varningar när dina API:er har kommenterats korrekt med den här semantiska informationen.
Den här artikeln innehåller en kort beskrivning av var och en av de nullbara referenstypattributen och hur du använder dem.
Vi börjar med ett exempel. Anta att biblioteket har följande API för att hämta en resurssträng. Den här metoden kompilerades ursprungligen i en nullbar omedveten kontext:
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
Föregående exempel följer det välbekanta Try*
mönstret i .NET. Det finns två referensparametrar för det här API:et key
message
: och . Det här API:et har följande regler som rör null-tillståndet för dessa parametrar:
- Anropare bör inte skickas
null
som argument förkey
. - Anropare kan skicka en variabel vars värde är
null
som argument förmessage
. TryGetMessage
Om metoden returnerartrue
är värdetmessage
för inte null. Om returvärdet ärfalse
är värdetmessage
null.
Regeln för key
kan uttryckas kortfattat: key
bör vara en referenstyp som inte kan nulleras. Parametern message
är mer komplex. Den tillåter en variabel som är null
som argument, men garanterar att out
argumentet inte null
är . För dessa scenarier behöver du ett rikare ordförråd för att beskriva förväntningarna. Attributet NotNullWhen
som beskrivs nedan beskriver null-tillståndet för argumentet som används för parametern message
.
Kommentar
Om du lägger till dessa attribut får kompilatorn mer information om reglerna för ditt API. När anropande kod kompileras i en nullaktiverad aktiverad kontext varnar kompilatorn anropare när de bryter mot dessa regler. De här attributen aktiverar inte fler kontroller av implementeringen.
Attribut | Kategori | Innebörd |
---|---|---|
AllowNull | Förutsättning | En parameter, ett fält eller en egenskap som inte är null kan vara null. |
Tillåt inteNull | Förutsättning | En nullbar parameter, ett fält eller en egenskap får aldrig vara null. |
MaybeNull | Postcondition | En parameter, ett fält, en egenskap eller ett returvärde som inte är null kan vara null. |
NotNull | Postcondition | En nullbar parameter, ett fält, en egenskap eller ett returvärde blir aldrig null. |
MaybeNullNär | Villkorsstyrd postkondition | Ett icke-nullbart argument kan vara null när metoden returnerar det angivna bool värdet. |
NotNullWhen | Villkorsstyrd postkondition | Ett null-argument är inte null när metoden returnerar det angivna bool värdet. |
NotNullIfNotNull | Villkorsstyrd postkondition | Ett returvärde, en egenskap eller ett argument är inte null om argumentet för den angivna parametern inte är null. |
MemberNotNull | Metod- och egenskapshjälpmetoder | Medlemmen i listan är inte null när metoden returneras. |
MemberNotNullWhen | Metod- och egenskapshjälpmetoder | Medlemmen i listan blir inte null när metoden returnerar det angivna bool värdet. |
DoesNotReturn | Oåtkomlig kod | En metod eller egenskap returnerar aldrig. Med andra ord utlöser det alltid ett undantag. |
DoesNotReturnIf | Oåtkomlig kod | Den här metoden eller egenskapen returnerar aldrig om den associerade bool parametern har det angivna värdet. |
Ovanstående beskrivningar är en snabbreferens till vad varje attribut gör. I följande avsnitt beskrivs beteendet och innebörden av dessa attribut mer noggrant.
Förhandsvillkor: AllowNull
och DisallowNull
Överväg en läs-/skrivegenskap som aldrig returneras null
eftersom den har ett rimligt standardvärde. Anropare skickar null
till den inställda åtkomstorn när de anger det till det standardvärdet. Tänk dig till exempel ett meddelandesystem som ber om ett skärmnamn i ett chattrum. Om inget anges genererar systemet ett slumpmässigt namn:
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName;
När du kompilerar föregående kod i en oförståelig kontext är allt bra. När du aktiverar nullbara referenstyper blir egenskapen ScreenName
en icke-nullbar referens. Det stämmer för get
accessorn: den returnerar null
aldrig . Anropare behöver inte kontrollera den returnerade egenskapen för null
. Men nu skapar egenskapen null
en varning. För att stödja den här typen av kod lägger du till System.Diagnostics.CodeAnalysis.AllowNullAttribute attributet i egenskapen, som du ser i följande kod:
[AllowNull]
public string ScreenName
{
get => _screenName;
set => _screenName = value ?? GenerateRandomScreenName();
}
private string _screenName = GenerateRandomScreenName();
Du kan behöva lägga till ett using
direktiv för System.Diagnostics.CodeAnalysis att använda detta och andra attribut som beskrivs i den här artikeln. Attributet tillämpas på egenskapen, inte på set
accessorn. Attributet AllowNull
anger förhandsvillkor och gäller endast argument. Accessorn get
har ett returvärde, men inga parametrar. Därför AllowNull
gäller attributet endast för set
accessorn.
Föregående exempel visar vad du ska leta efter när du lägger till attributet i AllowNull
ett argument:
- Det allmänna kontraktet för den variabeln är att den inte ska vara
null
, så du vill ha en referenstyp som inte kan upphävas. - Det finns scenarier där en anropare kan skickas
null
som argument, även om de inte är den vanligaste användningen.
Oftast behöver du det här attributet för egenskaper, eller in
, out
och ref
argument. Attributet AllowNull
är det bästa valet när en variabel vanligtvis inte är null, men du måste tillåta null
det som en förhandsvillkor.
Jämför det med scenarier för att använda DisallowNull
: Du använder det här attributet för att ange att ett argument av en nullbar referenstyp inte ska vara null
. Överväg en egenskap där null
är standardvärdet, men klienter kan bara ange det till ett värde som inte är null. Ta följande kod som exempel:
public string ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string _comment;
Föregående kod är det bästa sättet att uttrycka din design att ReviewComment
kan vara null
, men kan inte anges till null
. När den här koden är nullbar kan du uttrycka det här konceptet tydligare för anropare med hjälp av System.Diagnostics.CodeAnalysis.DisallowNullAttribute:
[DisallowNull]
public string? ReviewComment
{
get => _comment;
set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null");
}
string? _comment;
I en null-kontext ReviewComment
get
kan accessorn returnera standardvärdet null
för . Kompilatorn varnar för att den måste kontrolleras innan åtkomst. Dessutom varnar den uppringare för att anropare, även om det kan vara null
, inte uttryckligen bör ställa in den på null
. Attributet DisallowNull
anger också ett förhandsvillkor, det påverkar get
inte åtkomstgivaren. Du använder attributet DisallowNull
när du observerar följande egenskaper om:
- Variabeln kan finnas
null
i kärnscenarier, ofta när den först instansieras. - Variabeln ska inte uttryckligen anges till
null
.
Dessa situationer är vanliga i kod som ursprungligen var null omedveten. Det kan vara så att objektegenskaper anges i två distinkta initieringsåtgärder. Det kan vara så att vissa egenskaper endast anges när något asynkront arbete har slutförts.
Med attributen AllowNull
och DisallowNull
kan du ange att förhandsvillkor för variabler kanske inte matchar de nullbara anteckningarna för dessa variabler. Dessa ger mer information om egenskaperna för ditt API. Den här ytterligare informationen hjälper anropare att använda ditt API korrekt. Kom ihåg att du anger förhandsvillkor med hjälp av följande attribut:
- AllowNull: Ett argument som inte kan null-värdet kan vara null.
- Tillåt inteNull: Ett null-argument får aldrig vara null.
Postconditions: MaybeNull
och NotNull
Anta att du har en metod med följande signatur:
public Customer FindCustomer(string lastName, string firstName)
Du har förmodligen skrivit en metod som den här för att returnera null
när namnet som söktes inte hittades. Det null
visar tydligt att posten inte hittades. I det här exemplet skulle du förmodligen ändra returtypen från Customer
till Customer?
. Om du deklarerar returvärdet som en nullbar referenstyp anges avsikten med det här API:et tydligt:
public Customer? FindCustomer(string lastName, string firstName)
Av skäl som omfattas av generics nullability kan tekniken inte generera den statiska analys som matchar ditt API. Du kan ha en allmän metod som följer ett liknande mönster:
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Metoden returnerar null
när det sökta objektet inte hittas. Du kan klargöra att metoden returnerar null
när ett objekt inte hittas genom att lägga till anteckningen MaybeNull
i metodens retur:
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate)
Föregående kod informerar anropare om att returvärdet faktiskt kan vara null. Den informerar också kompilatorn om att metoden kan returnera ett null
uttryck även om typen inte är nullbar. När du har en allmän metod som returnerar en instans av dess typparameter T
kan du uttrycka att den aldrig returneras null
med hjälp NotNull
av attributet.
Du kan också ange att ett returvärde eller ett argument inte är null trots att typen är en nullbar referenstyp. Följande metod är en hjälpmetod som genererar om det första argumentet är null
:
public static void ThrowWhenNull(object value, string valueExpression = "")
{
if (value is null) throw new ArgumentNullException(nameof(value), valueExpression);
}
Du kan anropa den här rutinen på följande sätt:
public static void LogMessage(string? message)
{
ThrowWhenNull(message, $"{nameof(message)} must not be null");
Console.WriteLine(message.Length);
}
När du har aktiverat null-referenstyper vill du se till att koden ovan kompileras utan varningar. När metoden returneras är parametern value
garanterat inte null. Det är dock acceptabelt att anropa ThrowWhenNull
med en null-referens. Du kan ange value
en null-referenstyp och lägga till eftervillkoret NotNull
i parameterdeklarationen:
public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "")
{
_ = value ?? throw new ArgumentNullException(nameof(value), valueExpression);
// other logic elided
Föregående kod uttrycker det befintliga kontraktet tydligt: Anropare kan skicka en variabel med null
värdet, men argumentet är garanterat aldrig null om metoden returnerar utan att utlösa ett undantag.
Du anger ovillkorliga postkonditioner med hjälp av följande attribut:
- MaybeNull: Ett icke-nullbart returvärde kan vara null.
- NotNull: Ett null-returvärde blir aldrig null.
Villkorliga villkor: NotNullWhen
, MaybeNullWhen
och NotNullIfNotNull
Du är förmodligen bekant med string
metoden String.IsNullOrEmpty(String). Den här metoden returnerar true
när argumentet är null eller en tom sträng. Det är en form av null-check: Anropare behöver inte null-kontrollera argumentet om metoden returnerar false
. Om du vill göra en metod som denna nullbar medveten, anger du argumentet till en nullbar referenstyp och lägger till NotNullWhen
attributet:
bool IsNullOrEmpty([NotNullWhen(false)] string? value)
Det informerar kompilatorn om att all kod där returvärdet är false
inte behöver null-kontroller. Tillägget av attributet informerar kompilatorns statiska analys som IsNullOrEmpty
utför den nödvändiga null-kontrollen: när det returnerar false
är argumentet inte null
.
string? userInput = GetUserInput();
if (!string.IsNullOrEmpty(userInput))
{
int messageLength = userInput.Length; // no null check needed.
}
// null check needed on userInput here.
Metoden String.IsNullOrEmpty(String) kommenteras enligt ovan för .NET Core 3.0. Du kan ha liknande metoder i kodbasen som kontrollerar objektens status för null-värden. Kompilatorn känner inte igen anpassade null-kontrollmetoder och du måste lägga till anteckningarna själv. När du lägger till attributet vet kompilatorns statiska analys när den testade variabeln har markerats som null.
En annan användning för dessa attribut är Try*
mönstret. Postkonditionerna för ref
och out
argumenten förmedlas via returvärdet. Tänk på den här metoden som visades tidigare (i en nullbar inaktiverad kontext):
bool TryGetMessage(string key, out string message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message != null;
}
Föregående metod följer ett typiskt .NET-formspråk: returvärdet anger om message
värdet har angetts till det hittade värdet eller, om inget meddelande hittas, till standardvärdet. Om metoden returnerar true
är värdet message
för inte null, annars anges message
metoden till null.
I en null-aktiverad kontext kan du kommunicera detta formspråk med hjälp av attributet NotNullWhen
. När du kommenterar parametrar för null-referenstyper gör message
du ett string?
och lägger till ett attribut:
bool TryGetMessage(string key, [NotNullWhen(true)] out string? message)
{
if (_messageMap.ContainsKey(key))
message = _messageMap[key];
else
message = null;
return message is not null;
}
I föregående exempel är värdet message
för inte null när TryGetMessage
returnerar true
. Du bör kommentera liknande metoder i kodbasen på samma sätt: argumenten kan vara lika med null
och är kända för att inte vara null när metoden returnerar true
.
Det finns ett sista attribut som du också kan behöva. Ibland beror null-tillståndet för ett returvärde på null-tillståndet för ett eller flera argument. Dessa metoder returnerar ett värde som inte är null när vissa argument inte null
är . Om du vill kommentera dessa metoder korrekt använder du attributet NotNullIfNotNull
. Tänk på följande metod:
string GetTopLevelDomainFromFullUrl(string url)
url
Om argumentet inte är null är utdata inte null
. När null-referenser har aktiverats måste du lägga till fler anteckningar om ditt API kan acceptera ett null-argument. Du kan kommentera returtypen enligt följande kod:
string? GetTopLevelDomainFromFullUrl(string? url)
Det fungerar också, men tvingar ofta uppringare att implementera extra null
kontroller. Kontraktet är att returvärdet bara skulle vara null
när argumentet url
är null
. Om du vill uttrycka kontraktet kommenterar du den här metoden enligt följande kod:
[return: NotNullIfNotNull(nameof(url))]
string? GetTopLevelDomainFromFullUrl(string? url)
I föregående exempel används operatorn nameof
för parametern url
. Den funktionen är tillgänglig i C# 11. Innan C# 11 måste du ange namnet på parametern som en sträng. Returvärdet och argumentet har båda kommenterats med ?
indikerar att antingen kan vara null
. Attributet förtydligar vidare att returvärdet inte är null när url
argumentet inte null
är .
Du anger villkorsstyrda postkonditioner med hjälp av följande attribut:
- MaybeNullWhen: Ett icke-nullbart argument kan vara null när metoden returnerar det angivna
bool
värdet. - NotNullWhen: Ett nullbart argument är inte null när metoden returnerar det angivna
bool
värdet. - NotNullIfNotNull: Ett returvärde är inte null om argumentet för den angivna parametern inte är null.
Hjälpmetoder: MemberNotNull
och MemberNotNullWhen
Dessa attribut anger din avsikt när du har omstrukturerat vanlig kod från konstruktorer till hjälpmetoder. C#-kompilatorn analyserar konstruktorer och fältinitierare för att se till att alla referensfält som inte kan nulliseras har initierats innan varje konstruktor returnerar. C#-kompilatorn spårar dock inte fälttilldelningar via alla hjälpmetoder. Kompilatorn utfärdar en varning CS8618
när fält inte initieras direkt i konstruktorn, utan i stället i en hjälpmetod. Du lägger till i MemberNotNullAttribute en metoddeklaration och anger de fält som initieras till ett värde som inte är null i metoden. Tänk till exempel på följande exempel:
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();
}
}
Du kan ange flera fältnamn som argument för MemberNotNull
attributkonstruktorn.
Har MemberNotNullWhenAttribute ett bool
argument. Du använder MemberNotNullWhen
i situationer där din hjälpmetod returnerar ett bool
som anger om din hjälpmetod initierade fält.
Stoppa nullbar analys när metoden anropas kastar
Vissa metoder, vanligtvis undantagshjälpare eller andra verktygsmetoder, avslutar alltid genom att utlösa ett undantag. En hjälp kan också utlösa ett undantag baserat på värdet för ett booleskt argument.
I det första fallet kan du lägga till DoesNotReturnAttribute attributet i metoddeklarationen. Kompilatorns null-tillståndsanalys kontrollerar inte någon kod i en metod som följer ett anrop till en metod som kommenterats med DoesNotReturn
. Tänk på den här metoden:
[DoesNotReturn]
private void FailFast()
{
throw new InvalidOperationException();
}
public void SetState(object containedField)
{
if (containedField is null)
{
FailFast();
}
// containedField can't be null:
_field = containedField;
}
Kompilatorn utfärdar inga varningar efter anropet till FailFast
.
I det andra fallet lägger du till System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute attributet till en boolesk parameter för metoden. Du kan ändra föregående exempel på följande sätt:
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;
}
När värdet för argumentet matchar konstruktorns DoesNotReturnIf
värde utför kompilatorn ingen null-tillståndsanalys efter den metoden.
Sammanfattning
Om du lägger till nullbara referenstyper får du en inledande vokabulär för att beskriva dina API:ers förväntningar på variabler som kan vara null
. Attributen ger ett mer omfattande ordförråd för att beskriva nulltillståndet för variabler som förhandsvillkor och postkonditioner. Dessa attribut beskriver dina förväntningar tydligare och ger en bättre upplevelse för utvecklare som använder dina API:er.
När du uppdaterar bibliotek för en nullbar kontext lägger du till dessa attribut för att vägleda användare av dina API:er till rätt användning. De här attributen hjälper dig att fullständigt beskriva null-tillståndet för argument och returvärden.
- AllowNull: Ett fält, parameter eller egenskap som inte kan null-värdet kan vara null.
- Tillåt inteNull: Ett nullbart fält, en parameter eller en egenskap får aldrig vara null.
- MaybeNull: Ett fält, parameter, egenskap eller returvärde som inte kan null-värdet kan vara null.
- NotNull: Ett nullbart fält, en parameter, en egenskap eller ett returvärde blir aldrig null.
- MaybeNullWhen: Ett icke-nullbart argument kan vara null när metoden returnerar det angivna
bool
värdet. - NotNullWhen: Ett nullbart argument är inte null när metoden returnerar det angivna
bool
värdet. - NotNullIfNotNull: Ett parameter-, egenskaps- eller returvärde är inte null om argumentet för den angivna parametern inte är null.
- DoesNotReturn: En metod eller egenskap returnerar aldrig. Med andra ord utlöser det alltid ett undantag.
- DoesNotReturnIf: Den här metoden eller egenskapen returnerar aldrig om den associerade
bool
parametern har det angivna värdet.