22 kenmerken
22.1 Algemeen
Veel van de C#-taal stelt de programmeur in staat om declaratieve informatie op te geven over de entiteiten die in het programma zijn gedefinieerd. De toegankelijkheid van een methode in een klasse wordt bijvoorbeeld opgegeven door deze te decoreren met de method_modifiers public
, protected
en internal
private
.
Met C# kunnen programmeurs nieuwe soorten declaratieve informatie bedenken, die kenmerken worden genoemd. Programmeurs kunnen vervolgens kenmerken koppelen aan verschillende programma-entiteiten en kenmerkgegevens ophalen in een runtime-omgeving.
Opmerking: een framework kan bijvoorbeeld een
HelpAttribute
kenmerk definiëren dat kan worden geplaatst op bepaalde programma-elementen (zoals klassen en methoden) om een toewijzing van deze programma-elementen aan hun documentatie te bieden. eindnotitie
Kenmerken worden gedefinieerd via de declaratie van kenmerkklassen (§22.2), die positionele en benoemde parameters kunnen hebben (§22.2.3). Kenmerken worden gekoppeld aan entiteiten in een C#-programma met behulp van kenmerkspecificaties (§22.3) en kunnen tijdens runtime worden opgehaald als kenmerkinstanties (§22.4).
22.2 Kenmerkklassen
22.2.1 Algemeen
Een klasse die is afgeleid van de abstracte klasse System.Attribute
, hetzij direct of indirect, is een kenmerkklasse. De declaratie van een kenmerkklasse definieert een nieuw type kenmerk dat kan worden geplaatst op programma-entiteiten. Volgens conventie worden kenmerkklassen benoemd met een achtervoegsel van Attribute
. Het gebruik van een kenmerk kan dit achtervoegsel bevatten of weglaten.
Een algemene klassedeclaratie mag niet worden gebruikt System.Attribute
als een directe of indirecte basisklasse.
Voorbeeld:
public class B : Attribute {} public class C<T> : B {} // Error – generic cannot be an attribute
eindvoorbeeld
22.2.2 Kenmerkgebruik
Het kenmerk AttributeUsage
(§22.5.2) wordt gebruikt om te beschrijven hoe een kenmerkklasse kan worden gebruikt.
AttributeUsage
heeft een positionele parameter (§22.2.3) waarmee een kenmerkklasse de soorten programma-entiteiten kan opgeven waarop deze kan worden gebruikt.
Voorbeeld: In het volgende voorbeeld wordt een kenmerkklasse gedefinieerd met de naam
SimpleAttribute
die alleen op class_declarationen interface_declaration skan worden geplaatst, en worden verschillende toepassingen van hetSimple
kenmerk weergegeven.[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)] public class SimpleAttribute : Attribute { ... } [Simple] class Class1 {...} [Simple] interface Interface1 {...}
Hoewel dit kenmerk is gedefinieerd met de naam
SimpleAttribute
, wanneer dit kenmerk wordt gebruikt, kan hetAttribute
achtervoegsel worden weggelaten, wat resulteert in de korte naamSimple
. Het bovenstaande voorbeeld is dus semantisch gelijk aan het volgende[SimpleAttribute] class Class1 {...} [SimpleAttribute] interface Interface1 {...}
eindvoorbeeld
AttributeUsage
heeft een benoemde parameter (§22.2.3), aangeroepen AllowMultiple
, die aangeeft of het kenmerk meer dan één keer kan worden opgegeven voor een bepaalde entiteit. Als AllowMultiple
voor een kenmerkklasse waar is, is die kenmerkklasse een kenmerkklasse voor meerdere toepassingen en kan deze meerdere keren op een entiteit worden opgegeven. Als AllowMultiple
voor een kenmerkklasse onwaar is of als deze niet is opgegeven, is die kenmerkklasse een kenmerkklasse voor eenmalig gebruik en kan deze maximaal één keer op een entiteit worden opgegeven.
Voorbeeld: In het volgende voorbeeld wordt een kenmerkklasse voor meerdere toepassingen gedefinieerd met de naam
AuthorAttribute
en wordt een klassedeclaratie met twee toepassingen van hetAuthor
kenmerk weergegeven:[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class AuthorAttribute : Attribute { public string Name { get; } public AuthorAttribute(string name) => Name = name; } [Author("Brian Kernighan"), Author("Dennis Ritchie")] class Class1 { ... }
eindvoorbeeld
AttributeUsage
heeft een andere benoemde parameter (§22.2.3), die aangeeft Inherited
of het kenmerk, wanneer opgegeven op een basisklasse, ook wordt overgenomen door klassen die zijn afgeleid van die basisklasse. Als Inherited
voor een kenmerkklasse waar is, wordt dat kenmerk overgenomen. Als Inherited
voor een kenmerkklasse onwaar is, wordt dat kenmerk niet overgenomen. Als deze niet is opgegeven, is de standaardwaarde waar.
Een kenmerkklasse X
waaraan AttributeUsage
geen kenmerk is gekoppeld, zoals in
class X : Attribute { ... }
is gelijk aan het volgende:
[AttributeUsage(
AttributeTargets.All,
AllowMultiple = false,
Inherited = true)
]
class X : Attribute { ... }
22.2.3 Positionele en benoemde parameters
Kenmerkklassen kunnen positionele parameters en benoemde parameters hebben. Elke constructor voor een openbaar exemplaar voor een kenmerkklasse definieert een geldige reeks positionele parameters voor die kenmerkklasse. Elk niet-statisch openbaar veld en elke eigenschap voor lezen/schrijven voor een kenmerkklasse definieert een benoemde parameter voor de kenmerkklasse. Voor een eigenschap om een benoemde parameter te definiëren, moet deze eigenschap zowel een openbare get-accessor als een openbare set accessor hebben.
Voorbeeld: In het volgende voorbeeld wordt een kenmerkklasse gedefinieerd met
HelpAttribute
één positionele parameter,url
en één benoemde parameter,Topic
. Hoewel deze niet-statisch en openbaar is, definieert de eigenschapUrl
geen benoemde parameter, omdat deze niet lezen/schrijven is. Er worden ook twee toepassingen van dit kenmerk weergegeven:[AttributeUsage(AttributeTargets.Class)] public class HelpAttribute : Attribute { public HelpAttribute(string url) // url is a positional parameter { ... } // Topic is a named parameter public string Topic { get; set; } public string Url { get; } } [Help("http://www.mycompany.com/xxx/Class1.htm")] class Class1 { } [Help("http://www.mycompany.com/xxx/Misc.htm", Topic ="Class2")] class Class2 { }
eindvoorbeeld
22.2.4 Kenmerkparametertypen
De typen positionele en benoemde parameters voor een kenmerkklasse zijn beperkt tot de kenmerkparametertypen. Dit zijn:
- Een van de volgende typen:
bool
, ,byte
,char
,double
,float
, ,int
long
sbyte
short
string
uint
ulong
.ushort
- Het type
object
. - Het type
System.Type
. - Opsommingstypen.
- Eendimensionale matrices van de bovenstaande typen.
- Een constructorargument of openbaar veld dat geen van deze typen heeft, mag niet worden gebruikt als een positionele of benoemde parameter in een kenmerkspecificatie.
22.3 Kenmerkspecificatie
Kenmerkspecificatie is de toepassing van een eerder gedefinieerd kenmerk voor een programma-entiteit. Een kenmerk is een stukje aanvullende declaratieve informatie die is opgegeven voor een programma-entiteit. Kenmerken kunnen worden opgegeven op globaal bereik (om kenmerken op te geven voor de betreffende assembly of module) en voor type_declaration s (§14.7), class_member_declarations (§15.3), interface_member_declarations (§§14.7) 18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ declaraties(§15.8), elementen van parameter_lists (§15.6.2) en elementen van type_parameter_lists (§15.2.3).
Kenmerken worden opgegeven in kenmerksecties. Een kenmerksectie bestaat uit een paar vierkante haken, die een door komma's gescheiden lijst met een of meer kenmerken omsluiten. De volgorde waarin kenmerken worden opgegeven in een dergelijke lijst en de volgorde waarin secties die aan dezelfde programma-entiteit zijn gekoppeld, zijn niet significant. De kenmerkspecificaties[A][B]
, [B][A]
en [A, B]
[B, A]
zijn bijvoorbeeld gelijkwaardig.
global_attributes
: global_attribute_section+
;
global_attribute_section
: '[' global_attribute_target_specifier attribute_list ']'
| '[' global_attribute_target_specifier attribute_list ',' ']'
;
global_attribute_target_specifier
: global_attribute_target ':'
;
global_attribute_target
: identifier
;
attributes
: attribute_section+
;
attribute_section
: '[' attribute_target_specifier? attribute_list ']'
| '[' attribute_target_specifier? attribute_list ',' ']'
;
attribute_target_specifier
: attribute_target ':'
;
attribute_target
: identifier
| keyword
;
attribute_list
: attribute (',' attribute)*
;
attribute
: attribute_name attribute_arguments?
;
attribute_name
: type_name
;
attribute_arguments
: '(' ')'
| '(' positional_argument_list (',' named_argument_list)? ')'
| '(' named_argument_list ')'
;
positional_argument_list
: positional_argument (',' positional_argument)*
;
positional_argument
: argument_name? attribute_argument_expression
;
named_argument_list
: named_argument (',' named_argument)*
;
named_argument
: identifier '=' attribute_argument_expression
;
attribute_argument_expression
: non_assignment_expression
;
Voor de productie-global_attribute_target en in de onderstaande tekst heeft de id een spelling die gelijk is aan of assembly
, indien gelijkheid is gedefinieerd in module
. Voor de productie-attribute_target en in de onderstaande tekst heeft de id een spelling die niet gelijk is aan assembly
ofmodule
, met behulp van dezelfde definitie van gelijkheid als hierboven.
Een kenmerk bestaat uit een attribute_name en een optionele lijst met positionele en benoemde argumenten. De positionele argumenten (indien aanwezig) worden voorafgegaan door de benoemde argumenten. Een positioneel argument bestaat uit een attribute_argument_expression; een benoemd argument bestaat uit een naam, gevolgd door een gelijkteken, gevolgd door een attribute_argument_expression, die samen worden beperkt door dezelfde regels als eenvoudige toewijzing. De volgorde van benoemde argumenten is niet significant.
Opmerking: Voor het gemak is een volgkomma toegestaan in een global_attribute_section en een attribute_section, net zoals toegestaan in een array_initializer (§17.7). eindnotitie
De attribute_name identificeert een kenmerkklasse.
Wanneer een kenmerk op globaal niveau wordt geplaatst, is een global_attribute_target_specifier vereist. Wanneer de global_attribute_target gelijk is aan:
-
assembly
— het doel is de bevatde assembly -
module
— het doel is de module met
Er zijn geen andere waarden toegestaan voor global_attribute_target .
De gestandaardiseerde attribute_target namen zijn event
, , field
method
, param
, , property
, , return
en type
typevar
. Deze doelnamen worden alleen gebruikt in de volgende contexten:
-
event
— een gebeurtenis. -
field
— een veld. Een veldachtige gebeurtenis (bijvoorbeeld één zonder toegangsrechten) (§15.8.2) en een automatisch geïmplementeerde eigenschap (§15.7.4) kan ook een kenmerk met dit doel hebben. -
method
— een constructor, finalizer, methode, operator, eigenschap get and set accessors, indexeerfunctie get and set accessors, and event add and remove accessors. Een veldachtige gebeurtenis (bijvoorbeeld één zonder toegangsrechten) kan ook een kenmerk met dit doel hebben. -
param
— een accessor voor eigenschappensets, een indexeerfunctie die accessor instelt, toegangsrechten voor gebeurtenissen toevoegt en verwijdert, en een parameter in een constructor, methode en operator. -
property
— een eigenschap en een indexeerfunctie. -
return
— een gemachtigde, methode, operator, eigenschap get accessor en indexeerfunctie get accessor. -
type
— een gemachtigde, klasse, struct, opsomming en interface. -
typevar
— een typeparameter.
Bepaalde contexten staan de specificatie van een kenmerk toe voor meer dan één doel. Een programma kan het doel expliciet opgeven door een attribute_target_specifier op te geven. Zonder een attribute_target_specifier wordt een standaardwaarde toegepast, maar een attribute_target_specifier kan worden gebruikt om de standaardinstelling te bevestigen of te overschrijven. De contexten worden als volgt omgezet:
- Voor een kenmerk in een gedelegeerdedeclaratie is het standaarddoel de gemachtigde. Anders wanneer de attribute_target gelijk is aan:
-
type
— het doel is de gemachtigde -
return
— het doel de retourwaarde is
-
- Voor een kenmerk bij een methodedeclaratie is het standaarddoel de methode. Anders wanneer de attribute_target gelijk is aan:
-
method
— het doel is de methode -
return
— het doel de retourwaarde is
-
- Voor een kenmerk voor een operatordeclaratie is het standaarddoel de operator. Anders wanneer de attribute_target gelijk is aan:
-
method
— het doel is de operator -
return
— het doel de retourwaarde is
-
- Voor een kenmerk voor een declaratie van een get accessor voor een eigenschap of indexeerfunctiedeclaratie is het standaarddoel de bijbehorende methode. Anders wanneer de attribute_target gelijk is aan:
-
method
— het doel de bijbehorende methode is -
return
— het doel de retourwaarde is
-
- Voor een kenmerk dat is opgegeven voor een set accessor voor een eigenschap of indexeerfunctiedeclaratie, is het standaarddoel de bijbehorende methode. Anders wanneer de attribute_target gelijk is aan:
-
method
— het doel de bijbehorende methode is -
param
— het doel is de enige impliciete parameter
-
- Voor een kenmerk op een automatisch geïmplementeerde eigenschapsdeclaratie is het standaarddoel de eigenschap. Anders wanneer de attribute_target gelijk is aan:
-
field
— het doel is het door compiler gegenereerde backingveld voor de eigenschap
-
- Voor een kenmerk dat is opgegeven in een gebeurtenisdeclaratie die event_accessor_declarations het standaarddoel weglaat, is de gebeurtenisdeclaratie. Anders wanneer de attribute_target gelijk is aan:
-
event
— het doel is de gebeurtenisdeclaratie -
field
— het doel is het veld -
method
— de doelen zijn de methoden
-
- In het geval van een gebeurtenisdeclaratie die niet weglaat event_accessor_declarations het standaarddoel de methode is.
-
method
— het doel de bijbehorende methode is -
param
— het doel is de enige parameter
-
In alle andere contexten is het opnemen van een attribute_target_specifier toegestaan, maar niet nodig.
Voorbeeld: een klassedeclaratie kan de aanduiding
type
bevatten of weglaten:[type: Author("Brian Kernighan")] class Class1 {} [Author("Dennis Ritchie")] class Class2 {}
eindvoorbeeld.
Een implementatie kan andere attribute_target saccepteren, waarvan de implementatie is gedefinieerd. Een implementatie die een dergelijke attribute_target niet herkent, geeft een waarschuwing uit en negeert de met attribute_section.
Volgens conventie worden kenmerkklassen benoemd met een achtervoegsel van Attribute
. Een attribute_name kan dit achtervoegsel opnemen of weglaten. In het bijzonder wordt een attribute_name als volgt opgelost:
- Als de meest rechtse id van de attribute_name een exacte aanduiding is (§6.4.3), wordt de attribute_name omgezet als een type_name (§7.8). Als het resultaat geen type is dat is afgeleid van
System.Attribute
, treedt er een compilatietijdfout op. - Anders
- De attribute_name wordt opgelost als een type_name (§7.8), met uitzondering van fouten worden onderdrukt. Als deze oplossing is geslaagd en resulteert in een type dat is afgeleid van
System.Attribute
dat type, is het type het resultaat van deze stap. - De tekens
Attribute
worden toegevoegd aan de meest rechtse id in de attribute_name en de resulterende tekenreeks van tokens wordt omgezet als een type_name (§7.8), behalve fouten worden onderdrukt. Als deze oplossing is geslaagd en resulteert in een type dat is afgeleid vanSystem.Attribute
dat type, is het type het resultaat van deze stap.
- De attribute_name wordt opgelost als een type_name (§7.8), met uitzondering van fouten worden onderdrukt. Als deze oplossing is geslaagd en resulteert in een type dat is afgeleid van
Als precies één van de twee bovenstaande stappen resulteert in een type dat is afgeleid van System.Attribute
, is dat type het resultaat van de attribute_name. Anders treedt er een compilatietijdfout op.
Voorbeeld: Als een kenmerkklasse zowel met als zonder dit achtervoegsel wordt gevonden, is er sprake van dubbelzinnigheid en treedt er een compilatiefout op. Als de attribute_name zodanig is gespeld dat de meest rechtse id een exacte id is (§6.4.3), wordt alleen een kenmerk zonder achtervoegsel vergeleken, waardoor een dergelijke dubbelzinnigheid kan worden opgelost. Het voorbeeld
[AttributeUsage(AttributeTargets.All)] public class Example : Attribute {} [AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Error: ambiguity class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Refers to Example class Class3 {} [@ExampleAttribute] // Refers to ExampleAttribute class Class4 {}
toont twee kenmerkklassen met de naam
Example
enExampleAttribute
. Het kenmerk[Example]
is dubbelzinnig, omdat het kan verwijzen naarExample
ofExampleAttribute
. Als u een verbatim-id gebruikt, kan de exacte intentie in dergelijke zeldzame gevallen worden opgegeven. Het kenmerk[ExampleAttribute]
is niet dubbelzinnig (hoewel het zou zijn als er een kenmerkklasse met de naamExampleAttributeAttribute
!). Als de declaratie voor klasseExample
wordt verwijderd, verwijzen beide kenmerken als volgt naar de kenmerkklasse met de naamExampleAttribute
:[AttributeUsage(AttributeTargets.All)] public class ExampleAttribute : Attribute {} [Example] // Refers to ExampleAttribute class Class1 {} [ExampleAttribute] // Refers to ExampleAttribute class Class2 {} [@Example] // Error: no attribute named “Example” class Class3 {}
eindvoorbeeld
Het is een compilatiefout voor het gebruik van een kenmerkklasse voor eenmalig gebruik meer dan één keer op dezelfde entiteit.
Voorbeeld: Het voorbeeld
[AttributeUsage(AttributeTargets.Class)] public class HelpStringAttribute : Attribute { public HelpStringAttribute(string value) { Value = value; } public string Value { get; } } [HelpString("Description of Class1")] [HelpString("Another description of Class1")] // multiple uses not allowed public class Class1 {}
resulteert in een compilatietijdfout omdat deze probeert te gebruiken
HelpString
, wat een kenmerkklasse voor eenmalig gebruik is, meer dan één keer op de declaratie vanClass1
.eindvoorbeeld
Een expressie E
is een attribute_argument_expression als alle volgende instructies waar zijn:
- Het type
E
is een kenmerkparametertype (§22.2.4). - Tijdens het compileren kan de waarde worden
E
omgezet in een van de volgende:- Een constante waarde.
- Een
System.Type
object dat is verkregen met behulp van een typeof_expression (§12.8.18) die een niet-algemeen type, een gesloten geconstrueerd type (§8.4.3) of een niet-afhankelijk algemeen type (§8.4.4.4) aangeeft, maar geen open type (§8.4.3). - Een eendimensionale matrix van attribute_argument_expressions.
Voorbeeld:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)] public class TestAttribute : Attribute { public int P1 { get; set; } public Type P2 { get; set; } public object P3 { get; set; } } [Test(P1 = 1234, P3 = new int[]{1, 3, 5}, P2 = typeof(float))] class MyClass {} class C<T> { [Test(P2 = typeof(T))] // Error – T not a closed type. int x1; [Test(P2 = typeof(C<T>))] // Error – C<;T>; not a closed type. int x2; [Test(P2 = typeof(C<int>))] // Ok int x3; [Test(P2 = typeof(C<>))] // Ok int x4; }
eindvoorbeeld
De kenmerken van een type dat in meerdere onderdelen is gedeclareerd, worden bepaald door de kenmerken van elk van de onderdelen te combineren in een niet-opgegeven volgorde. Als hetzelfde kenmerk op meerdere onderdelen wordt geplaatst, is het gelijk aan het opgeven van dat kenmerk meerdere keren op het type.
Voorbeeld: De twee delen:
[Attr1, Attr2("hello")] partial class A {} [Attr3, Attr2("goodbye")] partial class A {}
zijn gelijk aan de volgende enkele declaratie:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")] class A {}
eindvoorbeeld
Kenmerken van typeparameters worden op dezelfde manier gecombineerd.
22.4 Kenmerkinstanties
22.4.1 Algemeen
Een kenmerkexemplaren is een exemplaar dat een kenmerk tijdens runtime vertegenwoordigt. Een kenmerk wordt gedefinieerd met een kenmerkklasse, positionele argumenten en benoemde argumenten. Een kenmerkexemplaren is een exemplaar van de kenmerkklasse die wordt geïnitialiseerd met de positionele en benoemde argumenten.
Het ophalen van een kenmerkexemplaren omvat zowel compileertijd- als runtimeverwerking, zoals beschreven in de volgende subclauses.
22.4.2 Compilatie van een kenmerk
De compilatie van een kenmerk met kenmerkklasseT
, positional_argument_listP
, en opgegeven op een programma-entiteit N
wordt gecompileerd in een assembly E
via de volgende stappen:
- Volg de stappen voor het compileren van een object_creation_expression van het nieuwe
T(P)
formulier. Deze stappen resulteren in een compilatiefout of bepalen op welke instantieconstructorC
T
tijdens runtime kan worden aangeroepen. - Als
C
er geen openbare toegankelijkheid is, treedt er een compilatietijdfout op. - Voor elke named_argument
Arg
inN
:- Laat
Name
de id van de named_argumentArg
zijn. -
Name
identificeert een niet-statisch openbaar veld of een niet-statische openbare veld of eigenschap opT
. AlsT
er geen veld of eigenschap is, treedt er een compilatietijdfout op.
- Laat
- Als een van de waarden in positional_argument_list
P
of een van de waarden binnen van het typeN
is en de waarde niet goed is gevormd zoals gedefinieerd door de Unicode-standaard, wordt er door de implementatie gedefinieerd of de gecompileerde waarde gelijk is aan de opgehaalde runtimewaarde (System.String
).Opmerking: Een tekenreeks die een hoge surrogaat-UTF-16-code-eenheid bevat die niet direct wordt gevolgd door een lage surrogaatcode-eenheid, is niet goed opgemaakt. eindnotitie
- Sla de volgende informatie (voor runtime-instantiëring van het kenmerk) op in de assembly-uitvoer door de compiler als gevolg van het compileren van het programma met het kenmerk: de kenmerkklasse
T
, de instantieconstructorC
opT
, de positional_argument_listP
, de named_argument_listN
en de bijbehorende programma-entiteitE
, waarbij de waarden volledig zijn opgelost tijdens het compileren.
22.4.3 Runtime ophalen van een kenmerkexemplaren
Met behulp van de termen die in §22.4.2 zijn gedefinieerd, kan het kenmerkexemplaar dat wordt vertegenwoordigd door T
, C
P
en , en N
die aan E
de assembly A
zijn gekoppeld, tijdens runtime worden opgehaald met behulp van de volgende stappen:
- Volg de uitvoeringsstappen voor het uitvoeren van een object_creation_expression van het formulier
new T(P)
met behulp van de instantieconstructorC
en -waarden zoals bepaald tijdens het compileren. Deze stappen resulteren in een uitzondering of produceren een exemplaarO
vanT
. - Voor elke named_argument
Arg
inN
volgorde:- Laat
Name
de id van de named_argumentArg
zijn. AlsName
er geen niet-statisch openbaar lees-/schrijfveld of -eigenschapO
wordt geïdentificeerd, wordt er een uitzondering gegenereerd. - Laten we
Value
het resultaat zijn van het evalueren van de attribute_argument_expression vanArg
. - Als
Name
u een veld aangeeftO
, stelt u dit veld in opValue
. - Anders identificeert naam een eigenschap op
O
. Stel deze eigenschap in op Waarde. - Het resultaat is
O
, een exemplaar van de kenmerkklasseT
die is geïnitialiseerd met de positional_argument_listP
en de named_argument_listN
.
- Laat
Opmerking: de indeling voor het opslaan
T
,C
, ,P
N
(en koppelen aanE
) inA
en het mechanisme voor het opgevenE
en ophalenT
C
P
van , ,N
vanA
(en dus hoe een kenmerkexemplaren tijdens runtime worden verkregen) valt buiten het bereik van deze specificatie. eindnotitie
Voorbeeld: In een implementatie van de CLI kunnen de
Help
kenmerkexemplaren in de assembly die zijn gemaakt door het voorbeeldprogramma in §22.2.3 te compileren, worden opgehaald met het volgende programma:public sealed class InterrogateHelpUrls { public static void Main(string[] args) { Type helpType = typeof(HelpAttribute); string assemblyName = args[0]; foreach (Type t in Assembly.Load(assemblyName).GetTypes()) { Console.WriteLine($"Type : {t}"); var attributes = t.GetCustomAttributes(helpType, false); var helpers = (HelpAttribute[]) attributes; foreach (var helper in helpers) { Console.WriteLine($"\tUrl : {helper.Url}"); } } } }
eindvoorbeeld
22.5 Gereserveerde kenmerken
22.5.1 Algemeen
Een aantal kenmerken is op een of andere manier van invloed op de taal. Deze kenmerken zijn onder andere:
-
System.AttributeUsageAttribute
(§22.5.2), dat wordt gebruikt om de manieren te beschrijven waarop een kenmerkklasse kan worden gebruikt. -
System.Diagnostics.ConditionalAttribute
(§22.5.3) is een kenmerkklasse voor meerdere toepassingen die wordt gebruikt voor het definiëren van voorwaardelijke methoden en voorwaardelijke kenmerkklassen. Dit kenmerk geeft een voorwaarde aan door een symbool voor voorwaardelijke compilatie te testen. -
System.ObsoleteAttribute
(§22.5.4), dat wordt gebruikt om een lid als verouderd te markeren. -
System.Runtime.CompilerServices.AsyncMethodBuilderAttribute
(§22.5.5), dat wordt gebruikt om een opbouwfunctie voor een asynchrone methode tot stand te brengen. -
System.Runtime.CompilerServices.CallerLineNumberAttribute
(§22.5.6.2),System.Runtime.CompilerServices.CallerFilePathAttribute
(§22.5.6.3) enSystem.Runtime.CompilerServices.CallerMemberNameAttribute
(§22.5.6.4), die worden gebruikt om informatie over de aanroepende context aan optionele parameters op te geven.
De statische analysekenmerken (§22.5.7) kunnen de juistheid van waarschuwingen die worden gegenereerd voor nullabiliteiten en null-statussen verbeteren (§8.9.5).
Een uitvoeringsomgeving kan aanvullende door de implementatie gedefinieerde kenmerken bieden die van invloed zijn op de uitvoering van een C#-programma.
22.5.2 Het kenmerk AttributeUsage
Het kenmerk AttributeUsage
wordt gebruikt om de manier te beschrijven waarop de kenmerkklasse kan worden gebruikt.
Een klasse die is versierd met het AttributeUsage
kenmerk, moet rechtstreeks of indirect worden afgeleid van System.Attribute
. Anders treedt er een compilatietijdfout op.
Opmerking: Zie §22.2.2 voor een voorbeeld van het gebruik van dit kenmerk. eindnotitie
22.5.3 Het kenmerk Voorwaardelijk
22.5.3.1 Algemeen
Het kenmerk Conditional
maakt de definitie van voorwaardelijke methoden en klassen van voorwaardelijke kenmerken mogelijk.
22.5.3.2 Voorwaardelijke methoden
Een methode die is ingericht met het Conditional
kenmerk is een voorwaardelijke methode. Elke voorwaardelijke methode wordt dus gekoppeld aan de symbolen voor voorwaardelijke compilatie die zijn gedeclareerd in de Conditional
kenmerken.
Voorbeeld:
class Eg { [Conditional("ALPHA")] [Conditional("BETA")] public static void M() { // ... } }
Eg.M
declareert als een voorwaardelijke methode die is gekoppeld aan de twee voorwaardelijke compilatiesymbolenALPHA
enBETA
.eindvoorbeeld
Een aanroep naar een voorwaardelijke methode wordt opgenomen als een of meer van de bijbehorende symbolen voor voorwaardelijke compilatie zijn gedefinieerd op het moment van aanroepen, anders wordt de aanroep weggelaten.
Een voorwaardelijke methode is onderworpen aan de volgende beperkingen:
- De voorwaardelijke methode is een methode in een class_declaration of struct_declaration. Er treedt een compilatiefout op als het
Conditional
kenmerk is opgegeven op een methode in een interfacedeclaratie. - De voorwaardelijke methode heeft een retourtype
void
. - De voorwaardelijke methode mag niet worden gemarkeerd met de
override
wijzigingsfunctie. Een voorwaardelijke methode kan echter worden gemarkeerd met devirtual
modifier. Onderdrukkingen van een dergelijke methode zijn impliciet voorwaardelijk en worden niet expliciet gemarkeerd met eenConditional
kenmerk. - De voorwaardelijke methode is geen implementatie van een interfacemethode. Anders treedt er een compilatietijdfout op.
- De parameters van de voorwaardelijke methode mogen geen uitvoerparameters zijn.
Daarnaast treedt er een compilatietijdfout op als een gemachtigde wordt gemaakt op basis van een voorwaardelijke methode.
Voorbeeld: Het voorbeeld
#define DEBUG using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void M() { Console.WriteLine("Executed Class1.M"); } } class Class2 { public static void Test() { Class1.M(); } }
Class1.M
declareert als een voorwaardelijke methode.Class2
DeTest
methode roept deze methode aan. Omdat het symbool voor voorwaardelijke compilatieDEBUG
is gedefinieerd, alsClass2.Test
het wordt aangeroepen, wordt het aangeroepenM
. Als het symboolDEBUG
niet was gedefinieerd, zou datClass2.Test
niet worden aangeroepenClass1.M
.eindvoorbeeld
Het is belangrijk om te begrijpen dat de opname of uitsluiting van een aanroep naar een voorwaardelijke methode wordt bepaald door de symbolen voor voorwaardelijke compilatie op het moment van de aanroep.
Voorbeeld: In de volgende code
// File Class1.cs: using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public static void F() { Console.WriteLine("Executed Class1.F"); } } // File Class2.cs: #define DEBUG class Class2 { public static void G() { Class1.F(); // F is called } } // File Class3.cs: #undef DEBUG class Class3 { public static void H() { Class1.F(); // F is not called } }
de klassen
Class2
enClass3
elk bevatten aanroepen naar de voorwaardelijke methodeClass1.F
, die voorwaardelijk is op basis van of deze al dan nietDEBUG
is gedefinieerd. Omdat dit symbool is gedefinieerd in de context vanClass2
maar nietClass3
, wordt de aanroep naarF
binnenClass2
opgenomen, terwijl de aanroep naarF
binnenClass3
wordt weggelaten.eindvoorbeeld
Het gebruik van voorwaardelijke methoden in een overnameketen kan verwarrend zijn. Aanroepen van een voorwaardelijke methode via base
, van het formulier base.M
, zijn onderworpen aan de normale regels voor het aanroepen van voorwaardelijke methoden.
Voorbeeld: In de volgende code
// File Class1.cs using System; using System.Diagnostics; class Class1 { [Conditional("DEBUG")] public virtual void M() => Console.WriteLine("Class1.M executed"); } // File Class2.cs class Class2 : Class1 { public override void M() { Console.WriteLine("Class2.M executed"); base.M(); // base.M is not called! } } // File Class3.cs #define DEBUG class Class3 { public static void Main() { Class2 c = new Class2(); c.M(); // M is called } }
Class2
bevat een aanroep naar deM
gedefinieerde in de basisklasse. Deze aanroep wordt weggelaten omdat de basismethode voorwaardelijk is op basis van de aanwezigheid van het symboolDEBUG
, wat niet is gedefinieerd. De methode schrijft dus alleen naar de console 'Class2.M executed
'. Judicious use of pp_declarations kan dergelijke problemen elimineren.eindvoorbeeld
22.5.3.3 Voorwaardelijke kenmerkklassen
Een kenmerkklasse (§22.2) die is ingericht met een of meer Conditional
kenmerken, is een voorwaardelijke kenmerkklasse. Een voorwaardelijke kenmerkklasse wordt dus gekoppeld aan de voorwaardelijke compilatiesymbolen die zijn gedeclareerd in de Conditional
kenmerken.
Voorbeeld:
[Conditional("ALPHA")] [Conditional("BETA")] public class TestAttribute : Attribute {}
TestAttribute
declareert als een voorwaardelijke kenmerkklasse die is gekoppeld aan de symbolen voor voorwaardelijke compilatiesALPHA
enBETA
.eindvoorbeeld
Kenmerkspecificaties (§22.3) van een voorwaardelijk kenmerk worden opgenomen als een of meer van de bijbehorende voorwaardelijke compilatiesymbolen zijn gedefinieerd op het moment van specificatie, anders wordt de kenmerkspecificatie weggelaten.
Het is belangrijk te weten dat de opname of uitsluiting van een kenmerkspecificatie van een voorwaardelijke kenmerkklasse wordt bepaald door de symbolen voor voorwaardelijke compilatie op het punt van de specificatie.
Voorbeeld: In het voorbeeld
// File Test.cs: using System; using System.Diagnostics; [Conditional("DEBUG")] public class TestAttribute : Attribute {} // File Class1.cs: #define DEBUG [Test] // TestAttribute is specified class Class1 {} // File Class2.cs: #undef DEBUG [Test] // TestAttribute is not specified class Class2 {}
de klassen
Class1
enClass2
zijn elk ingericht met kenmerkTest
, wat voorwaardelijk is op basis van ofDEBUG
niet is gedefinieerd. Omdat dit symbool is gedefinieerd in de context vanClass1
maar nietClass2
, wordt de specificatie van het kenmerkClass1
Test opgenomen, terwijl de specificatie van hetTest
kenmerkClass2
wordt weggelaten.eindvoorbeeld
22.5.4 Het verouderde kenmerk
Het kenmerk Obsolete
wordt gebruikt om typen en leden van typen te markeren die niet meer moeten worden gebruikt.
Als een programma gebruikmaakt van een type of lid dat is ingericht met het kenmerk Obsolete
, geeft een compiler een waarschuwing of een fout. In het bijzonder geeft een compiler een waarschuwing als er geen foutparameter wordt opgegeven, of als de foutparameter is opgegeven en de waarde heeft false
. Een compiler geeft een fout op als de foutparameter is opgegeven en de waarde true
heeft.
Voorbeeld: In de volgende code
[Obsolete("This class is obsolete; use class B instead")] class A { public void F() {} } class B { public void F() {} } class Test { static void Main() { A a = new A(); // Warning a.F(); } }
de klasse
A
is ingericht met hetObsolete
kenmerk. Elk gebruik vanA
in resultaten resulteert inMain
een waarschuwing die het opgegeven bericht bevat: 'Deze klasse is verouderd; gebruik in plaats daarvan klasseB
".eindvoorbeeld
22.5.5 Het kenmerk AsyncMethodBuilder
Dit kenmerk wordt beschreven in §15.15.1.
22.5.6 Kenmerken van beller-info
22.5.6.1 Algemeen
Voor doeleinden zoals logboekregistratie en rapportage is het soms handig voor een functielid om bepaalde compilatiegegevens over de aanroepende code te verkrijgen. De kenmerken van de beller-info bieden een manier om dergelijke informatie transparant door te geven.
Wanneer een optionele parameter wordt geannoteerd met een van de kenmerken van de aanroeper-info, zorgt het weglaten van het bijbehorende argument in een aanroep er niet voor dat de standaardparameterwaarde wordt vervangen. Als in plaats daarvan de opgegeven informatie over de aanroepende context beschikbaar is, wordt die informatie doorgegeven als argumentwaarde.
Voorbeeld:
public void Log( [CallerLineNumber] int line = -1, [CallerFilePath] string path = null, [CallerMemberName] string name = null ) { Console.WriteLine((line < 0) ? "No line" : "Line "+ line); Console.WriteLine((path == null) ? "No file path" : path); Console.WriteLine((name == null) ? "No member name" : name); }
Een aanroep naar
Log()
zonder argumenten zou het regelnummer en bestandspad van de oproep afdrukken, evenals de naam van het lid waarin de aanroep heeft plaatsgevonden.eindvoorbeeld
Kenmerken van aanroepergegevens kunnen overal optionele parameters voorkomen, inclusief in declaraties voor gemachtigden. De specifieke kenmerken van de aanroeper-info hebben echter beperkingen voor de typen parameters die ze kunnen toewijzen, zodat er altijd een impliciete conversie van een vervangende waarde naar het parametertype zal zijn.
Er is een fout opgetreden bij het hebben van hetzelfde kenmerk voor aanroepergegevens voor een parameter van zowel het definiëren als implementeren van een deel van een gedeeltelijke methodedeclaratie. Alleen kenmerken van de aanroeper-info in het definiërende onderdeel worden toegepast, terwijl kenmerken van aanroepergegevens die alleen in het implementatieonderdeel optreden, worden genegeerd.
Informatie over beller heeft geen invloed op overbelastingsresolutie. Aangezien de toegeschreven optionele parameters nog steeds worden weggelaten uit de broncode van de aanroeper, worden deze parameters genegeerd op dezelfde manier als andere weggelaten optionele parameters (§12.6.4).
Aanroepergegevens worden alleen vervangen wanneer een functie expliciet wordt aangeroepen in de broncode. Impliciete aanroepen, zoals impliciete bovenliggende constructor-aanroepen, hebben geen bronlocatie en vervangen de aanroepergegevens niet. Oproepen die dynamisch zijn gebonden, vervangen ook geen gegevens van bellers. Wanneer een aanroeper-info toegeschreven parameter wordt weggelaten in dergelijke gevallen, wordt in plaats daarvan de opgegeven standaardwaarde van de parameter gebruikt.
Een uitzondering hierop zijn query-expressies. Deze worden beschouwd als syntactische uitbreidingen en als de aanroepen die ze uitbreiden om optionele parameters met kenmerken van de beller-info weg te laten, worden aanroepergegevens vervangen. De gebruikte locatie is de locatie van de querycomponent waaruit de aanroep is gegenereerd.
Als meer dan één aanroeper-infokenmerk is opgegeven voor een bepaalde parameter, worden ze herkend in de volgende volgorde: CallerLineNumber
, CallerFilePath
, . CallerMemberName
Houd rekening met de volgende parameterdeclaratie:
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
CallerLineNumber
heeft voorrang en de andere twee kenmerken worden genegeerd. Als CallerLineNumber
dit wordt weggelaten, CallerFilePath
krijgt u voorrang en CallerMemberName
wordt deze genegeerd. De lexicale volgorde van deze kenmerken is irrelevant.
22.5.6.2 Het kenmerk CallerLineNumber
Het kenmerk System.Runtime.CompilerServices.CallerLineNumberAttribute
is toegestaan voor optionele parameters wanneer er een standaard impliciete conversie is (§10.4.2) van de constante waarde int.MaxValue
tot het type van de parameter. Dit zorgt ervoor dat een niet-negatief regelnummer tot die waarde zonder fouten kan worden doorgegeven.
Als een functieaanroep vanaf een locatie in broncode een optionele parameter weglaat met de CallerLineNumberAttribute
, wordt een numerieke letterlijke waarde die het regelnummer van die locatie vertegenwoordigt, gebruikt als argument voor de aanroep in plaats van de standaardparameterwaarde.
Als de aanroep meerdere regels omvat, is de gekozen lijn afhankelijk van de implementatie.
Het regelnummer kan worden beïnvloed door #line
richtlijnen (§6.5.8).
22.5.6.3 Het kenmerk CallerFilePath
Het kenmerk System.Runtime.CompilerServices.CallerFilePathAttribute
is toegestaan voor optionele parameters wanneer er een standaard impliciete conversie is (§10.4.2) van string
het type parameter.
Als een functieaanroep vanaf een locatie in broncode een optionele parameter weglaat met de CallerFilePathAttribute
, wordt een letterlijke tekenreeks die het bestandspad van die locatie vertegenwoordigt, gebruikt als argument voor de aanroep in plaats van de standaardparameterwaarde.
De indeling van het bestandspad is afhankelijk van de implementatie.
Het bestandspad kan worden beïnvloed door #line
richtlijnen (§6.5.8).
22.5.6.4 Het kenmerk CallerMemberName
Het kenmerk System.Runtime.CompilerServices.CallerMemberNameAttribute
is toegestaan voor optionele parameters wanneer er een standaard impliciete conversie is (§10.4.2) van string
het type parameter.
Als een functieaanroep vanuit een locatie in de hoofdtekst van een functielid of binnen een kenmerk dat is toegepast op het functielid zelf of het retourtype, parameters of typeparameters in de broncode een optionele parameter weglaat met de CallerMemberNameAttribute
, dan wordt een letterlijke tekenreeks die de naam van dat lid vertegenwoordigt, gebruikt als argument voor de aanroep in plaats van de standaardparameterwaarde.
Voor aanroepen die plaatsvinden binnen algemene methoden, wordt alleen de naam van de methode zelf gebruikt, zonder de typeparameterlijst.
Voor aanroepen die plaatsvinden binnen expliciete implementaties van interfaceleden, wordt alleen de naam van de methode zelf gebruikt, zonder de voorgaande interfacekwalificatie.
Voor aanroepen die plaatsvinden binnen eigenschaps- of gebeurtenistoegangsors, is de gebruikte lidnaam die van de eigenschap of gebeurtenis zelf.
Voor aanroepen die voorkomen in indexeerfunctietoegangsors, is de naam van het lid dat wordt gebruikt door een IndexerNameAttribute
(§22.6) op het lid van de indexeerfunctie, indien aanwezig, of de standaardnaam Item
anders.
Voor aanroepen die plaatsvinden binnen veld- of gebeurtenis-initialisaties, is de gebruikte lidnaam de naam van het veld of de gebeurtenis die wordt geïnitialiseerd.
Voor aanroepen die voorkomen in declaraties van exemplaarconstructors, statische constructors, finalizers en operators is de gebruikte lidnaam afhankelijk van de implementatie.
22.5.7 Codeanalysekenmerken
22.5.7.1 Algemeen
De kenmerken in deze sectie worden gebruikt om aanvullende informatie te bieden voor de ondersteuning van een compiler die nullability en null-state diagnostics biedt (§8.9.5). Een compiler is niet vereist voor het uitvoeren van null-statusdiagnose. De aanwezigheid of afwezigheid van deze kenmerken heeft geen invloed op de taal of het gedrag van een programma. Een compiler die geen diagnostische status null-status biedt, leest en negeert de aanwezigheid van deze kenmerken. Een compiler die diagnostische gegevens over null-status biedt, gebruikt de betekenis die in deze sectie is gedefinieerd voor een van deze kenmerken die worden gebruikt om de diagnostische gegevens ervan te informeren.
De kenmerken van de code-analyse worden gedeclareerd in de naamruimte System.Diagnostics.CodeAnalysis
.
Attribuut | Betekenis |
---|---|
AllowNull (§22.5.7.2) |
Een niet-null-argument kan null zijn. |
DisallowNull (§22.5.7.3) |
Een null-argument mag nooit null zijn. |
MaybeNull (§22.5.7.6) |
Een niet-null-retourwaarde kan null zijn. |
NotNull (§22.5.7.8) |
Een retourwaarde die null kan worden geretourneerd, is nooit null. |
MaybeNullWhen (§22.5.7.7) |
Een niet-null-argument kan null zijn wanneer de methode de opgegeven bool waarde retourneert. |
NotNullWhen (§22.5.7.10) |
Een null-argument is niet null wanneer de methode de opgegeven bool waarde retourneert. |
NotNullIfNotNull (§22.5.7.9) |
Een retourwaarde is niet null als het argument voor de opgegeven parameter niet null is. |
DoesNotReturn (§22.5.7.4) |
Deze methode retourneert nooit. |
DoesNotReturnIf (§22.5.7.5) |
Deze methode retourneert nooit als de bijbehorende bool parameter de opgegeven waarde heeft. |
De volgende secties in §22.5.7.1 zijn voorwaardelijk normatief.
22.5.7.2 Het kenmerk AllowNull
Hiermee geeft u op dat een null-waarde is toegestaan als invoer, zelfs als het bijbehorende type dit niet toelaat.
Voorbeeld: Houd rekening met de volgende lees-/schrijfeigenschap die nooit wordt geretourneerd
null
omdat deze een redelijke standaardwaarde heeft. Een gebruiker kan echter null aan de set accessor geven om de eigenschap in te stellen op die standaardwaarde.#nullable enable public class X { [AllowNull] public string ScreenName { get => _screenName; set => _screenName = value ?? GenerateRandomScreenName(); } private string _screenName = GenerateRandomScreenName(); private static string GenerateRandomScreenName() => ...; }
Gezien het volgende gebruik van de set accessor van die eigenschap
var v = new X(); v.ScreenName = null; // may warn without attribute AllowNull
Zonder het kenmerk kan een compiler een waarschuwing genereren omdat de eigenschap met een niet-op-null ingesteld type lijkt te zijn ingesteld op een nul-waarde. De aanwezigheid van het kenmerk onderdrukt die waarschuwing. eindvoorbeeld
22.5.7.3 Het kenmerk DisallowNull
Hiermee geeft u op dat een null-waarde niet is toegestaan als invoer, zelfs als het bijbehorende type dit toestaat.
Voorbeeld: Houd rekening met de volgende eigenschap waarin null de standaardwaarde is, maar clients kunnen deze alleen instellen op een niet-null-waarde.
#nullable enable public class X { [DisallowNull] public string? ReviewComment { get => _comment; set => _comment = value ?? throw new ArgumentNullException(nameof(value), "Cannot set to null"); } private string? _comment = default; }
De get-accessor kan de standaardwaarde van
null
retourneren, zodat een compiler kan waarschuwen dat deze moet worden gecontroleerd voordat de toegang wordt geopend. Bovendien waarschuwt het bellers dat bellers, ook al kan het null zijn, bellers deze niet expliciet moeten instellen op null. eindvoorbeeld
22.5.7.4 Het kenmerk DoesNotReturn
Hiermee geeft u op dat een bepaalde methode nooit retourneert.
Voorbeeld: Houd rekening met het volgende:
public class X { [DoesNotReturn] private void FailFast() => throw new InvalidOperationException(); public void SetState(object? containedField) { if ((!isInitialized) || (containedField == null)) { FailFast(); } // null check not needed. _field = containedField; } private bool isInitialized = false; private object _field; }
De aanwezigheid van het kenmerk helpt een compiler op verschillende manieren. Ten eerste kan een compiler een waarschuwing geven als er een pad is waar de methode kan worden afgesloten zonder een uitzondering te genereren. Ten tweede kan een compiler null-waarschuwingen in code onderdrukken na een aanroep naar die methode, totdat een geschikte catch-component is gevonden. Ten derde heeft de onbereikbare code geen invloed op null-statussen.
Het kenmerk wijzigt de bereikbaarheid (§13.2) of de definitieve toewijzingsanalyse (§9.4) niet op basis van de aanwezigheid van dit kenmerk. Deze wordt alleen gebruikt om null-baarheidswaarschuwingen te beïnvloeden. eindvoorbeeld
22.5.7.5 Het kenmerk DoesNotReturnIf
Hiermee geeft u op dat een bepaalde methode nooit retourneert als de bijbehorende bool
parameter de opgegeven waarde heeft.
Voorbeeld: Houd rekening met het volgende:
#nullable enable public class X { private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName) { if (!isNull) { throw new ArgumentException(argumentName, $"argument {argumentName} can't be null"); } } public void SetFieldState(object containedField) { ThrowIfNull(containedField == null, nameof(containedField)); // unreachable code when "isInitialized" is false: _field = containedField; } private bool isInitialized = false; private object _field = default!; }
eindvoorbeeld
22.5.7.6 Het kenmerk MaybeNull
Hiermee geeft u op dat een niet-nullbare retourwaarde null mag zijn.
Voorbeeld: Bekijk de volgende algemene methode:
#nullable enable public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
Het idee van deze code is dat als
T
deze wordt vervangen doorstring
,T?
een nullable annotatie wordt. Deze code is echter niet legaal, omdatT
deze niet beperkt is tot een verwijzingstype. Als u dit kenmerk toevoegt, wordt het probleem echter opgelost:#nullable enable [return: MaybeNull] public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }
Het kenmerk informeert bellers dat het contract een niet-null-type impliceert, maar de retourwaarde kan daadwerkelijk zijn
null
. eindvoorbeeld
22.5.7.7 Het kenmerk MaybeNullWhen
Hiermee geeft u op dat een niet-null-argument kan zijn null
wanneer de methode de opgegeven bool
waarde retourneert. Dit is vergelijkbaar met het MaybeNull
kenmerk (§22.5.7.6), maar bevat een parameter voor de opgegeven retourwaarde.
22.5.7.8 Het kenmerk NotNull
Hiermee geeft u op dat een null-waarde nooit zal zijn null
als de methode retourneert (in plaats van te gooien).
Voorbeeld: Houd rekening met het volgende:
#nullable enable public static void ThrowWhenNull([NotNull] object? value, string valueExpression = "") => _ = value ?? throw new ArgumentNullException(valueExpression); public static void LogMessage(string? message) { ThrowWhenNull(message, nameof(message)); Console.WriteLine(message.Length); }
Wanneer null-verwijzingstypen zijn ingeschakeld, compileert de methode
ThrowWhenNull
zonder waarschuwingen. Wanneer deze methode wordt geretourneerd, is hetvalue
argument gegarandeerd nietnull
. Het is echter acceptabel om aan te roepenThrowWhenNull
met een null-verwijzing. eindvoorbeeld
22.5.7.9 Het kenmerk NotNullIfNotNull
Hiermee geeft u op dat een retourwaarde niet null
is als het argument voor de opgegeven parameter niet null
is.
Voorbeeld: De null-status van een retourwaarde kan afhankelijk zijn van de null-status van een of meer argumenten. Om de analyse van een compiler te helpen wanneer een methode altijd een niet-null-waarde retourneert wanneer bepaalde argumenten niet
null
kan het kenmerkNotNullIfNotNull
worden gebruikt. Houd rekening met de volgende methode:#nullable enable string GetTopLevelDomainFromFullUrl(string url) { ... }
Als het
url
argument nietnull
is,null
wordt dit niet geretourneerd. Wanneer null-verwijzingen zijn ingeschakeld, werkt die handtekening correct, mits de API nooit een null-argument accepteert. Als het argument echter null kan zijn, kan de retourwaarde ook null zijn. Als u dat contract correct wilt uitdrukken, moet u deze methode als volgt aantekenen:#nullable enable [return: NotNullIfNotNull("url")] string? GetTopLevelDomainFromFullUrl(string? url) { ... }
eindvoorbeeld
22.5.7.10 Het kenmerk NotNullWhen
Hiermee geeft u op dat een null-argument niet zal zijn null
wanneer de methode de opgegeven bool
waarde retourneert.
Voorbeeld: De bibliotheekmethode
String.IsNullOrEmpty(String)
retourneerttrue
wanneer het argument of een lege tekenreeks isnull
. Dit is een vorm van null-controle: bellers hoeven het argument niet te controleren als de methode retourneertfalse
. Als u een methode zoals deze op null-waarde wilt toepassen, moet u het parametertype een null-referentietype maken en het kenmerk NotNullWhen toevoegen:#nullable enable bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }
eindvoorbeeld
22.6 Kenmerken voor interoperation
Voor interoperation met andere talen kan een indexeerfunctie worden geïmplementeerd met behulp van geïndexeerde eigenschappen. Als er geen IndexerName
kenmerk aanwezig is voor een indexeerfunctie, wordt de naam Item
standaard gebruikt. Met IndexerName
het kenmerk kan een ontwikkelaar deze standaardwaarde overschrijven en een andere naam opgeven.
Voorbeeld: Standaard is
Item
de naam van een indexeerfunctie . Dit kan als volgt worden overschreven:[System.Runtime.CompilerServices.IndexerName("TheItem")] public int this[int index] { get { ... } set { ... } }
Nu is
TheItem
de naam van de indexeerfunctie.eindvoorbeeld
ECMA C# draft specification