Delen via


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, protecteden internalprivate.

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 het Simple 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 het Attribute achtervoegsel worden weggelaten, wat resulteert in de korte naam Simple. 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 het Author 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 Inheritedof 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, urlen één benoemde parameter, Topic. Hoewel deze niet-statisch en openbaar is, definieert de eigenschap Url 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, , intlongsbyteshortstringuintulong. 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, , fieldmethod, param, , property, , returnen typetypevar. 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 typebevatten 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 van System.Attribute dat type, is het type het resultaat van deze stap.

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 en ExampleAttribute. Het kenmerk [Example] is dubbelzinnig, omdat het kan verwijzen naar Example of ExampleAttribute. 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 naam ExampleAttributeAttribute!). Als de declaratie voor klasse Example wordt verwijderd, verwijzen beide kenmerken als volgt naar de kenmerkklasse met de naam ExampleAttribute:

[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 van Class1.

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 instantieconstructor CT tijdens runtime kan worden aangeroepen.
  • Als C er geen openbare toegankelijkheid is, treedt er een compilatietijdfout op.
  • Voor elke named_argumentArg in N:
    • Laat Name de id van de named_argumentArgzijn.
    • Name identificeert een niet-statisch openbaar veld of een niet-statische openbare veld of eigenschap op T. Als T er geen veld of eigenschap is, treedt er een compilatietijdfout op.
  • Als een van de waarden in positional_argument_listP of een van de waarden binnen van het type N 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 kenmerkklasseT, de instantieconstructor C 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, CPen , en Ndie 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 instantieconstructor C en -waarden zoals bepaald tijdens het compileren. Deze stappen resulteren in een uitzondering of produceren een exemplaar O van T.
  • Voor elke named_argumentArg in Nvolgorde:
    • Laat Name de id van de named_argumentArgzijn. Als Name er geen niet-statisch openbaar lees-/schrijfveld of -eigenschap Owordt geïdentificeerd, wordt er een uitzondering gegenereerd.
    • Laten we Value het resultaat zijn van het evalueren van de attribute_argument_expression van Arg.
    • Als Name u een veld aangeeft O, stelt u dit veld in op Value.
    • Anders identificeert naam een eigenschap op O. Stel deze eigenschap in op Waarde.
    • Het resultaat is O, een exemplaar van de kenmerkklasse T die is geïnitialiseerd met de positional_argument_listP en de named_argument_listN.

Opmerking: de indeling voor het opslaan T, C, , PN (en koppelen aan E) in A en het mechanisme voor het opgeven E en ophalen TCPvan , , N van A (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) en System.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 compilatiesymbolen ALPHA en BETA.

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 de virtual modifier. Onderdrukkingen van een dergelijke methode zijn impliciet voorwaardelijk en worden niet expliciet gemarkeerd met een Conditional 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. Class2De Test methode roept deze methode aan. Omdat het symbool voor voorwaardelijke compilatie DEBUG is gedefinieerd, als Class2.Test het wordt aangeroepen, wordt het aangeroepen M. Als het symbool DEBUG niet was gedefinieerd, zou dat Class2.Test niet worden aangeroepen Class1.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 en Class3 elk bevatten aanroepen naar de voorwaardelijke methode Class1.F, die voorwaardelijk is op basis van of deze al dan niet DEBUG is gedefinieerd. Omdat dit symbool is gedefinieerd in de context van Class2 maar niet Class3, wordt de aanroep naar F binnen Class2 opgenomen, terwijl de aanroep naar F binnen Class3 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 de M gedefinieerde in de basisklasse. Deze aanroep wordt weggelaten omdat de basismethode voorwaardelijk is op basis van de aanwezigheid van het symbool DEBUG, 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 compilaties ALPHA en BETA.

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 en Class2 zijn elk ingericht met kenmerk Test, wat voorwaardelijk is op basis van of DEBUG niet is gedefinieerd. Omdat dit symbool is gedefinieerd in de context van Class1 maar niet Class2, wordt de specificatie van het kenmerk Class1 Test opgenomen, terwijl de specificatie van het Test kenmerk Class2 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 trueheeft.

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 het Obsolete kenmerk. Elk gebruik van A in resultaten resulteert in Main een waarschuwing die het opgegeven bericht bevat: 'Deze klasse is verouderd; gebruik in plaats daarvan klasse B ".

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 nullretourneren, 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 door string, T? een nullable annotatie wordt. Deze code is echter niet legaal, omdat T 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 het value argument gegarandeerd niet null. Het is echter acceptabel om aan te roepen ThrowWhenNull 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 nullis.

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 kenmerk NotNullIfNotNull worden gebruikt. Houd rekening met de volgende methode:

#nullable enable
string GetTopLevelDomainFromFullUrl(string url) { ... }

Als het url argument niet nullis, 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) retourneert true wanneer het argument of een lege tekenreeks is null . Dit is een vorm van null-controle: bellers hoeven het argument niet te controleren als de methode retourneert false. 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 Itemde naam van een indexeerfunctie . Dit kan als volgt worden overschreven:

[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
    get { ... }
    set { ... }
}

Nu is TheItemde naam van de indexeerfunctie.

eindvoorbeeld