Dela via


22 attribut

22.1 Allmänt

Mycket av C#-språket gör det möjligt för programmeraren att ange deklarativ information om de entiteter som definierats i programmet. Till exempel anges tillgängligheten för en metod i en klass genom att den dekoreras med method_modifiers public, protected, internaloch private.

C# gör det möjligt för programmerare att uppfinna nya typer av deklarativ information, som kallas attribut. Programmerare kan sedan koppla attribut till olika programentiteter och hämta attributinformation i en körningsmiljö.

Obs! Ett ramverk kan till exempel definiera ett HelpAttribute attribut som kan placeras på vissa programelement (till exempel klasser och metoder) för att tillhandahålla en mappning från dessa programelement till dokumentationen. slutkommentar

Attribut definieras genom deklarationen av attributklasser (§22.2), som kan ha positionella och namngivna parametrar (§22.2.3). Attribut kopplas till entiteter i ett C#-program med hjälp av attributspecifikationer (§22.3) och kan hämtas vid körning som attributinstanser (§22.4).

22.2 Attributklasser

22.2.1 Allmänt

En klass som härleds från den abstrakta klassen System.Attribute, antingen direkt eller indirekt, är en attributklass. Deklarationen av en attributklass definierar en ny typ av attribut som kan placeras på programentiteter. Enligt konventionen namnges attributklasser med suffixet Attribute. Användning av ett attribut kan antingen innehålla eller utelämna det här suffixet.

En generisk klassdeklaration får inte användas System.Attribute som en direkt eller indirekt basklass.

Exempel:

public class B : Attribute {}
public class C<T> : B {} // Error – generic cannot be an attribute

slutexempel

22.2.2 Attributanvändning

Attributet AttributeUsage (§22.5.2) används för att beskriva hur en attributklass kan användas.

AttributeUsage har en positionsparameter (§22.2.3) som gör det möjligt för en attributklass att ange vilka typer av programentiteter som den kan användas på.

Exempel: I följande exempel definieras en attributklass med namnet SimpleAttribute som kan placeras på class_declarations och endast interface_declarations och visar flera användningsområden för Simple attributet.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public class SimpleAttribute : Attribute
{ 
    ... 
}

[Simple] class Class1 {...}
[Simple] interface Interface1 {...}

Även om det här attributet definieras med namnet SimpleAttribute, när det här attributet används, kan suffixet Attribute utelämnas, vilket resulterar i det korta namnet Simple. Exemplet ovan är alltså semantiskt likvärdigt med följande

[SimpleAttribute] class Class1 {...}
[SimpleAttribute] interface Interface1 {...}

slutexempel

AttributeUsage har en namngiven parameter (§22.2.3), med namnet AllowMultiple, som anger om attributet kan anges mer än en gång för en viss entitet. Om AllowMultiple för en attributklass är sant är den attributklassen en attributklass med flera användningsområden och kan anges mer än en gång i en entitet. Om AllowMultiple för en attributklass är false eller om den är ospecificerad är den attributklassen en attributklass för en enda användning och kan anges högst en gång i en entitet.

Exempel: I följande exempel definieras en attributklass med flera användningsområden med namnet AuthorAttribute och en klassdeklaration med två användningsområden för Author attributet:

[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 
{
    ...
}

slutexempel

AttributeUsage har en annan namngiven parameter (§22.2.3), kallad Inherited, som anger om attributet, när det anges i en basklass, också ärvs av klasser som härleds från den basklassen. Om Inherited attributet för en attributklass är sant ärvs det attributet. Om Inherited attributet för en attributklass är falskt ärvs inte det attributet. Om det är ospecificerat är standardvärdet sant.

En attributklass X som inte har ett AttributeUsage attribut kopplat till sig, som i

class X : Attribute { ... }

motsvarar följande:

[AttributeUsage(
   AttributeTargets.All,
   AllowMultiple = false,
   Inherited = true)
]
class X : Attribute { ... }

22.2.3 Positionella och namngivna parametrar

Attributklasser kan ha positionsparametraroch namngivna parameterer. Varje offentlig instanskonstruktor för en attributklass definierar en giltig sekvens med positionsparametrar för den attributklassen. Varje icke-statiskt offentligt skrivfält och -egenskap för en attributklass definierar en namngiven parameter för attributklassen. För att en egenskap ska definiera en namngiven parameter ska den egenskapen ha både en offentlig get-accessor och en offentlig set-accessor.

Exempel: I följande exempel definieras en attributklass med namnet HelpAttribute som har en positionsparameter, url, och en namngiven parameter, Topic. Även om det är icke-statiskt och offentligt, definierar egenskapen Url inte en namngiven parameter, eftersom den inte är skrivskyddad. Två användningsområden för det här attributet visas också:

[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
{
}

slutexempel

22.2.4 Attributparametertyper

Typerna av positionella och namngivna parametrar för en attributklass är begränsade till attributparametertyperna, som är:

  • En av följande typer: , , , , , boolbyte, , char, double, float, , int, long, , sbyte. shortstringuintulongushort
  • Typen object.
  • Typen System.Type.
  • Uppräkningstyper.
  • Endimensionella matriser av ovanstående typer.
  • Ett konstruktorargument eller offentligt fält som inte har någon av dessa typer ska inte användas som en positionell eller namngiven parameter i en attributspecifikation.

22.3 Attributspecifikation

Attributspecifikation är tillämpningen av ett tidigare definierat attribut för en programentitet. Ett attribut är en del av ytterligare deklarativ information som anges för en programentitet. Attribut kan anges i globalt omfång (för att ange attribut för den innehållande sammansättningen eller modulen) och för type_declaration s (§14.7), class_member_declaration s (§15.3), interface_member_declaration s (§14.7), class_member_declarations (§15.3), interface_member_declarations (§18.4), struct_member_declarations (§16.3), enum_member_declarations (§19.2), accessor_declarations (§15.7.3), event_accessor_ deklarations (§15.8), element i parameter_lists (§15.6.2) och element i type_parameter_lists (§15.2.3).

Attribut anges i attributavsnitt. Ett attributavsnitt består av ett par hakparenteser som omger en kommaavgränsad lista med ett eller flera attribut. Den ordning i vilken attribut anges i en sådan lista och i vilken ordning avsnitt som är kopplade till samma programentitet är ordnade är inte betydande. Attributspecifikationerna [A][B], [B][A], [A, B]och [B, A] är till exempel likvärdiga.

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
    ;

För produktion global_attribute_target, och i texten nedan, ska identifieraren ha en stavning som är lika med eller assembly, där likhet är den som definieras i module. För produktion attribute_target, och i texten nedan, ska identifieraren ha en stavning som inte är lika med assembly eller module, med samma definition av likhet som ovan.

Ett attribut består av en attribute_name och en valfri lista med positionella och namngivna argument. Positionsargumenten (om några) föregår de namngivna argumenten. Ett positionsargument består av en attribute_argument_expression. Ett namngivet argument består av ett namn följt av ett likhetstecken följt av en attribute_argument_expression, som tillsammans begränsas av samma regler som enkel tilldelning. Ordningen på namngivna argument är inte betydande.

Obs! För enkelhetens skull tillåts ett avslutande kommatecken i en global_attribute_section och en attribute_section, precis som en tillåts i en array_initializer (§17.7). slutkommentar

Attribute_name identifierar en attributklass.

När ett attribut placeras på global nivå krävs en global_attribute_target_specifier . När global_attribute_target är lika med:

  • assembly — Målet är den innehållande sammansättningen
  • module — målet är den modul som innehåller

Inga andra värden för global_attribute_target tillåts.

De standardiserade attribute_target namnen är event, field, method, param, property, return, , typeoch typevar. Dessa målnamn får endast användas i följande sammanhang:

  • event — en händelse.
  • field — ett fält. En fältliknande händelse (dvs. en utan accessorer) (§15.8.2) och en automatiskt implementerad egenskap (§15.7.4) kan också ha ett attribut med detta mål.
  • method — en konstruktor, slutförare, metod, operatör, egenskaps hämta och ange accessorer, indexerare hämta och ange accessorer och händelse lägga till och ta bort accessorer. En fältliknande händelse (t.ex. en utan accessorer) kan också ha ett attribut med det här målet.
  • param – en egenskapsuppsättningsåtkomstor, en indexerare, händelsetillägg och borttagning av accessorer samt en parameter i en konstruktor, metod och operator.
  • property — en egenskap och en indexerare.
  • return — en ombud, metod, operatör, egenskap få accessor och indexerare få accessor.
  • type — ombud, klass, struct, uppräkning och gränssnitt.
  • typevar – en typparameter.

Vissa kontexter tillåter specifikationen av ett attribut på mer än ett mål. Ett program kan uttryckligen ange målet genom att inkludera en attribute_target_specifier. Utan en attribute_target_specifier tillämpas ett standardvärde, men en attribute_target_specifier kan användas för att bekräfta eller åsidosätta standardvärdet. Kontexterna löses på följande sätt:

  • För ett attribut i en delegatdeklaration är standardmålet ombudet. Annars när attribute_target är lika med:
    • type — Målet är ombudet
    • return — Målet är returvärdet
  • För ett attribut i en metoddeklaration är standardmålet metoden. Annars när attribute_target är lika med:
    • method — Målet är metoden
    • return — Målet är returvärdet
  • För ett attribut i en operatordeklaration är standardmålet operatorn. Annars när attribute_target är lika med:
    • method — Målet är operatorn
    • return — Målet är returvärdet
  • För ett attribut på en get-åtkomstdeklaration för en egenskap eller indexeringsdeklaration är standardmålet den associerade metoden. Annars när attribute_target är lika med:
    • method — målet är den associerade metoden
    • return — Målet är returvärdet
  • För ett attribut som anges för en uppsättningsåtkomst för en egenskap eller indexeringsdeklaration är standardmålet den associerade metoden. Annars när attribute_target är lika med:
    • method — målet är den associerade metoden
    • param — målet är den ensamma implicita parametern
  • För ett attribut på en automatiskt implementerad egenskapsdeklaration är standardmålet egenskapen. Annars när attribute_target är lika med:
    • field – målet är det kompilatorgenererade bakgrundsfältet för egenskapen
  • För ett attribut som anges i en händelsedeklaration som utelämnar event_accessor_declarations är standardmålet händelsedeklarationen. Annars när attribute_target är lika med:
    • event — Målet är händelsedeklarationen
    • field — Målet är fältet
    • method — Målen är metoderna
  • När det gäller en händelsedeklaration som inte utelämnar event_accessor_declarations är standardmålet metoden.
    • method — målet är den associerade metoden
    • param — målet är den ensamma parametern

I alla andra sammanhang är inkludering av en attribute_target_specifier tillåten men onödig.

Exempel: en klassdeklaration kan antingen inkludera eller utelämna specificeraren type:

[type: Author("Brian Kernighan")]
class Class1 {}

[Author("Dennis Ritchie")]
class Class2 {}

slutexempel.

En implementering kan acceptera andra attribute_target, vars syften är implementeringsdefinierade. En implementering som inte känner igen en sådan attribute_target ska utfärda en varning och ignorera den innehållande attribute_section.

Enligt konventionen namnges attributklasser med suffixet Attribute. En attribute_name kan antingen inkludera eller utelämna det här suffixet. Mer specifikt löses en attribute_name på följande sätt:

  • Om den mest högra identifieraren för attribute_name är en ordagrann identifierare (§6.4.3) löses attribute_name som en type_name (§7.8). Om resultatet inte är en typ som härleds från System.Attributeuppstår ett kompileringsfel.
  • Annars
    • Attribute_name löses som en type_name (§7.8) förutom att eventuella fel undertrycks. Om den här lösningen lyckas och resulterar i en typ som härletts från System.Attribute är typen resultatet av det här steget.
    • Tecknen Attribute läggs till i den högra identifieraren i attribute_name och den resulterande strängen med token löses som en type_name (§7.8) förutom att eventuella fel ignoreras. Om den här lösningen lyckas och resulterar i en typ som härletts från System.Attribute är typen resultatet av det här steget.

Om exakt ett av de två stegen ovan resulterar i en typ som härletts från System.Attributeär den typen resultatet av attribute_name. Annars uppstår ett kompileringsfel.

Exempel: Om en attributklass hittas både med och utan det här suffixet finns en tvetydighet och ett kompileringsfelresultat. Om attribute_name stavas så att dess mest högra identifierare är en ordagrann identifierare (§6.4.3), matchas endast ett attribut utan suffix, vilket gör det möjligt att lösa en sådan tvetydighet. Exemplet

[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 {}

visar två attributklasser med namnet Example och ExampleAttribute. Attributet [Example] är tvetydigt eftersom det kan referera till antingen Example eller ExampleAttribute. Med en ordagrann identifierare kan den exakta avsikten anges i sådana sällsynta fall. Attributet [ExampleAttribute] är inte tvetydigt (även om det skulle vara om det fanns en attributklass med namnet ExampleAttributeAttribute!). Om deklarationen för klassen Example tas bort refererar båda attributen till attributklassen med namnet ExampleAttribute, enligt följande:

[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 {}

slutexempel

Det är ett kompileringsfel att använda en attributklass för en enda användning mer än en gång på samma entitet.

Exempel: Exemplet

[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 {}

resulterar i ett kompileringsfel eftersom det försöker använda HelpString, vilket är en attributklass för en enda användning, mer än en gång i deklarationen av Class1.

slutexempel

Ett uttryck E är en attribute_argument_expression om alla följande instruktioner är sanna:

  • Typen av E är en attributparametertyp (§22.2.4).
  • Vid kompilering kan värdet E för matchas till något av följande:
    • Ett konstant värde.
    • Ett System.Type objekt som erhålls med hjälp av en typeof_expression (§12.8.18) som anger en icke-generisk typ, en sluten konstruktionstyp (§8.4.3) eller en obundna generisk typ (§8.4.4), men inte en öppen typ (§8.4.3).
    • En endimensionell matris med attribute_argument_expressions.

Exempel:

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

slutexempel

Attributen för en typ som deklareras i flera delar bestäms genom att attributen för var och en av dess delar kombineras i en ospecificerad ordning. Om samma attribut placeras på flera delar motsvarar det att ange attributet flera gånger för typen.

Exempel: De två delarna:

[Attr1, Attr2("hello")]
partial class A {}

[Attr3, Attr2("goodbye")]
partial class A {}

motsvarar följande enda deklaration:

[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}

slutexempel

Attribut för typparametrar kombineras på samma sätt.

22.4 Attributinstanser

22.4.1 Allmänt

En attributinstans är en instans som representerar ett attribut vid körning. Ett attribut definieras med en attributklass, positionsargument och namngivna argument. En attributinstans är en instans av attributklassen som initieras med de positionella och namngivna argumenten.

Hämtning av en attributinstans omfattar både kompileringstid och körningsbearbetning, enligt beskrivningen i följande underklienter.

22.4.2 Kompilering av ett attribut

Kompilering av ett attribut med attributklassen T, positional_argument_listP, och som anges i en programentitet N kompileras till en sammansättning E via följande steg:

  • Följ stegen för kompileringstidsbearbetning för att kompilera en object_creation_expression av formulärets nya T(P). De här stegen resulterar antingen i ett kompileringsfel eller fastställer en instanskonstruktor CT som kan anropas vid körning.
  • Om C det inte finns någon offentlig tillgänglighet uppstår ett kompileringsfel.
  • För varje named_argumentArg i N:
    • Låt oss Name vara identifierarenför named_argument Arg.
    • Name ska identifiera ett offentligt fält eller en egenskap för icke-statiskt skrivskyddat fält eller en egenskap på T. Om T det inte finns något sådant fält eller en sådan egenskap uppstår ett kompileringsfel.
  • Om något av värdena inom eller något av värdena inom P är av typen N och värdet inte är välformat enligt Unicode Standard definieras det om det kompilerade värdet är lika med det körningsvärde som hämtas (System.String).

    Obs! Till exempel är en sträng som innehåller en kodenhet med hög surrogattyp UTF-16 som inte omedelbart följs av en låg surrogatkodenhet inte välformulerad. slutkommentar

  • Lagra följande information (för körningsinstansiering av attributet) i sammansättningsutdata av kompilatorn som ett resultat av kompileringen av programmet som innehåller attributet: attributklassen T, instanskonstruktorn CT, positional_argument_listP, named_argument_listN och den associerade programentiteten E, med värdena lösta helt vid kompileringstid.

22.4.3 Körningshämtning av en attributinstans

Med hjälp av de termer som definieras i §22.4.2 kan attributinstansen som representeras av , T, Coch och Psom är associerad med N hämtas vid körning från sammansättningen E med hjälp av Aföljande steg:

  • Följ stegen för körningsbearbetning för att köra en . De här stegen resulterar antingen i ett undantag eller skapar en instans O av T.
  • För varje named_argumentArg i N, i ordning:
    • Låt oss Name vara identifierarenför named_argument Arg. Om Name inte identifierar ett icke-statiskt offentligt läs-skrivfält eller -egenskap på Ogenereras ett undantag.
    • Låt oss Value vara resultatet av utvärderingen av attribute_argument_expression av Arg.
    • Om Name identifierar ett fält på Oanger du fältet till Value.
    • Annars identifierar Name en egenskap på O. Ange den här egenskapen till Värde.
    • Resultatet är O, en instans av attributklassen T som har initierats med positional_argument_listP och named_argument_listN.

Obs! Formatet för att Tlagra , C, P( N och associera det med E) i A och mekanismen för att ange E och hämta T, C, Pfrån NA (och därmed hur en attributinstans hämtas vid körning) ligger utanför omfånget för den här specifikationen. slutkommentar

Exempel: I en implementering av CLI Help kan attributinstanserna i sammansättningen som skapats genom kompilering av exempelprogrammet i §22.2.3 hämtas med följande program:

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

slutexempel

22.5 Reserverade attribut

22.5.1 Allmänt

Ett antal attribut påverkar språket på något sätt. Dessa attribut är:

  • System.AttributeUsageAttribute (§22.5.2), som används för att beskriva hur en attributklass kan användas.
  • System.Diagnostics.ConditionalAttribute (§22.5.3), är en attributklass med flera användningsområden som används för att definiera villkorsstyrda metoder och klasser för villkorsstyrda attribut. Det här attributet anger ett villkor genom att testa en villkorlig kompileringssymbol.
  • System.ObsoleteAttribute (§22.5.4), som används för att markera en medlem som föråldrad.
  • System.Runtime.CompilerServices.AsyncMethodBuilderAttribute (§22.5.5), som används för att upprätta en aktivitetsbyggare för en asynkron metod.
  • System.Runtime.CompilerServices.CallerLineNumberAttribute (§22.5.6.2), System.Runtime.CompilerServices.CallerFilePathAttribute (§22.5.6.3) och System.Runtime.CompilerServices.CallerMemberNameAttribute (§22.5.6.4), som används för att tillhandahålla information om samtalskontexten till valfria parametrar.

Attributen för statisk analys som kan ogiltigförklaras (§22.5.7) kan förbättra korrektheten i varningar som genereras för null-värden och nulltillstånd (§8.9.5).

En körningsmiljö kan ge ytterligare implementeringsdefinierade attribut som påverkar körningen av ett C#-program.

22.5.2 AttributeUsage-attributet

Attributet AttributeUsage används för att beskriva hur attributklassen kan användas.

En klass som är dekorerad med AttributeUsage attributet ska härledas från System.Attribute, antingen direkt eller indirekt. Annars uppstår ett kompileringsfel.

Obs! Ett exempel på hur du använder det här attributet finns i §22.2.2. slutkommentar

22.5.3 Villkorsattributet

22.5.3.1 Allmänt

Attributet Conditional aktiverar definitionen av villkorsstyrda metoder och klasser för villkorsstyrda attribut.

22.5.3.2 Villkorsstyrda metoder

En metod som är dekorerad med Conditional attributet är en villkorsstyrd metod. Varje villkorsstyrd metod associeras därför med de villkorsstyrda kompileringssymbolerna som deklareras i dess Conditional attribut.

Exempel:

class Eg
{
    [Conditional("ALPHA")]
    [Conditional("BETA")]
    public static void M()
    {
        // ...
    }
}

deklarerar som en villkorsstyrd metod som är associerad med de två villkorliga kompileringssymbolerna Eg.MALPHA och BETA.

slutexempel

Ett anrop till en villkorsstyrd metod ingår om en eller flera av dess associerade villkorsstyrda kompileringssymboler definieras vid anropspunkten, annars utelämnas anropet.

En villkorsstyrd metod omfattas av följande begränsningar:

  • Villkorsmetoden ska vara en metod i en class_declaration eller struct_declaration. Ett kompileringsfel inträffar om Conditional attributet anges på en metod i en gränssnittsdeklaration.
  • Villkorsmetoden ska ha en returtyp av void.
  • Den villkorsstyrda metoden får inte markeras med override modifieraren. En villkorsstyrd metod kan dock markeras med virtual modifieraren. Åsidosättningar av en sådan metod är implicit villkorade och ska inte uttryckligen markeras med ett Conditional attribut.
  • Den villkorsstyrda metoden får inte vara en implementering av en gränssnittsmetod. Annars uppstår ett kompileringsfel.
  • Parametrarna för den villkorsstyrda metoden får inte vara utdataparametrar.

Dessutom uppstår ett kompileringsfel om ett ombud skapas från en villkorsstyrd metod.

Exempel: Exemplet

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

deklareras som en villkorsstyrd Class1.M metod. Class2's-metoden Test anropar den här metoden. Eftersom den villkorliga kompileringssymbolen DEBUG har definierats anropas om Class2.Test den Manropas . Om symbolen DEBUG inte hade definierats skulle den Class2.Test inte anropa Class1.M.

slutexempel

Det är viktigt att förstå att inkludering eller exkludering av ett anrop till en villkorsstyrd metod styrs av de villkorsstyrda kompileringssymbolerna vid tidpunkten för anropet.

Exempel: I följande kod

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

klasserna Class2 och var och Class3 en innehåller anrop till den villkorsstyrda metoden Class1.F, som är villkorad baserat på om har definierats eller inte DEBUG . Eftersom den här symbolen definieras i kontexten för Class2 men inte Class3inkluderas anropet till F i Class2 , medan anropet till F i Class3 utelämnas.

slutexempel

Användningen av villkorsstyrda metoder i en arvskedja kan vara förvirrande. Anrop som görs till en villkorsstyrd metod via base, i formuläret base.M, omfattas av de normala anropsreglerna för villkorsstyrd metod.

Exempel: I följande kod

// 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 innehåller ett anrop till den M definierade i basklassen. Det här anropet utelämnas eftersom basmetoden är villkorad baserat på förekomsten av symbolen DEBUG, som är odefinierad. Metoden skriver därför endast till -konsolenClass2.M executed. Omdömesgill användning av pp_declarations kan eliminera sådana problem.

slutexempel

22.5.3.3 Villkorliga attributklasser

En attributklass (§22.2) som är dekorerad med ett eller flera Conditional attribut är en villkorsstyrd attributklass. En villkorsstyrd attributklass associeras därför med de villkorsstyrda kompileringssymbolerna som deklareras i dess Conditional attribut.

Exempel:

[Conditional("ALPHA")]
[Conditional("BETA")]
public class TestAttribute : Attribute {}

deklarerar som en villkorsstyrd TestAttribute attributklass som är associerad med de villkorsstyrda kompileringssymbolerna ALPHA och BETA.

slutexempel

Attributspecifikationer (§22.3) för ett villkorsattribut inkluderas om en eller flera av dess associerade villkorsstyrda kompileringssymboler definieras vid specifikationspunkten, annars utelämnas attributspecifikationen.

Det är viktigt att observera att inkludering eller exkludering av en attributspecifikation för en villkorsstyrd attributklass styrs av de villkorsstyrda kompileringssymbolerna vid specifikationens punkt.

Exempel: I exemplet

// 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 {}

klasserna Class1 och Class2 är dekorerade med attributet Test, som är villkorsstyrd baserat på om de har definierats eller inte DEBUG . Eftersom den här symbolen definieras i kontexten för Class1 men inte Class2inkluderas specifikationen för testattributet på Class1 , medan specifikationen Test för attributet på Class2 utelämnas.

slutexempel

22.5.4 Attributet föråldrad

Attributet Obsolete används för att markera typer och medlemmar av typer som inte längre ska användas.

Om ett program använder en typ eller medlem som är dekorerad med attributet Obsolete, ska en kompilator utfärda en varning eller ett fel. Mer specifikt ska en kompilator utfärda en varning om det inte finns någon felparameter eller om felparametern anges och har värdet false. En kompilator ska utfärda ett fel om felparametern anges och har värdet true.

Exempel: I följande kod

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

klassen A är dekorerad med attributet Obsolete . Varje användning av A i resulterar i Main en varning som innehåller det angivna meddelandet "Den här klassen är föråldrad; använda klassen B i stället".

slutexempel

22.5.5 Attributet AsyncMethodBuilder

Detta attribut beskrivs i §15.15.1.

22.5.6 Caller-info-attribut

22.5.6.1 Allmänt

För ändamål som loggning och rapportering är det ibland användbart för en funktionsmedlem att hämta viss kompileringstidsinformation om den anropande koden. Attributen caller-info ger ett sätt att skicka sådan information transparent.

När en valfri parameter kommenteras med något av anropar-info-attributen, innebär utelämnande av motsvarande argument i ett anrop inte nödvändigtvis att standardparametervärdet ersätts. Om den angivna informationen om anropskontexten i stället är tillgänglig skickas den informationen som argumentvärde.

Exempel:

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

Ett anrop till utan argument skulle skriva ut linjenumret och filsökvägen för Log() anropet, samt namnet på medlemmen där anropet inträffade.

slutexempel

Anropar-info-attribut kan förekomma på valfria parametrar var som helst, inklusive i ombudsdeklarationer. De specifika anropar-info-attributen har dock begränsningar för vilka typer av parametrar de kan tillskriva, så att det alltid blir en implicit konvertering från ett ersatt värde till parametertypen.

Det är ett fel att ha samma anropar-info-attribut på en parameter för både definiera och implementera en del av en partiell metoddeklaration. Endast anropar-info-attribut i den definierande delen tillämpas, medan attribut för anropare-info som endast förekommer i implementeringsdelen ignoreras.

Uppringarens information påverkar inte överbelastningsupplösningen. Eftersom de tillskrivna valfria parametrarna fortfarande utelämnas från anroparens källkod ignorerar överlagringsmatchningen dessa parametrar på samma sätt som andra utelämnade valfria parametrar (§12.6.4).

Uppringarinformation ersätts endast när en funktion uttryckligen anropas i källkoden. Implicita anrop, till exempel implicita överordnade konstruktoranrop, har ingen källplats och ersätter inte uppringarinformation. Dessutom ersätter inte anrop som är dynamiskt bundna uppringarinformation. När en tillskriven parameter för anropare-info utelämnas i sådana fall används det angivna standardvärdet för parametern i stället.

Ett undantag är frågeuttryck. Dessa betraktas som syntaktiska expansioner, och om anropen de expanderar för att utelämna valfria parametrar med anropar-info-attribut ersätts uppringarens information. Platsen som används är platsen för frågesatsen som anropet genererades från.

Om fler än ett anropar-info-attribut anges för en viss parameter identifieras de i följande ordning: CallerLineNumber, CallerFilePath, CallerMemberName. Överväg följande parameterdeklaration:

[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...

CallerLineNumber har företräde och de andra två attributen ignoreras. Om CallerLineNumber utelämnades skulle CallerFilePath ha företräde och CallerMemberName ignoreras. Den lexikala ordningen på dessa attribut är irrelevant.

22.5.6.2 Attributet CallerLineNumber

Attributet System.Runtime.CompilerServices.CallerLineNumberAttribute tillåts för valfria parametrar när det finns en implicit standardkonvertering (§10.4.2) från konstantvärdet int.MaxValue till parameterns typ. Detta säkerställer att alla icke-negativa radnummer upp till det värdet kan skickas utan fel.

Om ett funktionsanrop från en plats i källkod utelämnar en valfri parameter med CallerLineNumberAttribute, används en numerisk literal som representerar platsens radnummer som ett argument till anropet i stället för standardparametervärdet.

Om anropet sträcker sig över flera rader är den valda raden implementeringsberoende.

Linjenumret kan påverkas av #line direktiv (§6.5.8).

22.5.6.3 Attributet CallerFilePath

Attributet System.Runtime.CompilerServices.CallerFilePathAttribute tillåts för valfria parametrar när det finns en implicit standardkonvertering (§10.4.2) från string parameterns typ.

Om en funktionsanrop från en plats i källkod utelämnar en valfri parameter med CallerFilePathAttribute, används en strängliteral som representerar platsens filsökväg som ett argument till anropet i stället för standardparametervärdet.

Filsökvägens format är implementeringsberoende.

Filsökvägen kan påverkas av #line direktiv (§6.5.8).

22.5.6.4 Attributet CallerMemberName

Attributet System.Runtime.CompilerServices.CallerMemberNameAttribute tillåts för valfria parametrar när det finns en implicit standardkonvertering (§10.4.2) från string parameterns typ.

Om ett funktionsanrop från en plats i en funktionsmedlems brödtext eller inom ett attribut som tillämpas på själva funktionsmedlemmen eller dess returtyp utelämnar parametrar eller typparametrar i källkoden en valfri parameter med CallerMemberNameAttribute, används en strängliteral som representerar namnet på medlemmen som ett argument till anropet i stället för standardparametervärdet.

För anrop som inträffar inom generiska metoder används endast själva metodnamnet, utan listan över typparametrar.

För anrop som sker inom explicita gränssnittsmedlemimplementeringar används endast själva metodnamnet, utan föregående gränssnittskvalifikation.

För anrop som inträffar i egenskaps- eller händelseåtkomster är det medlemsnamn som används egenskapen eller själva händelsen.

För anrop som inträffar inom indexerare är det medlemsnamn som används det som tillhandahålls av en IndexerNameAttribute (§22.6) på indexerarens medlem, om det finns, eller standardnamnet Item på annat sätt.

För anrop som inträffar inom fält- eller händelseinitierare är det medlemsnamn som används namnet på fältet eller händelsen som initieras.

För anrop som inträffar inom deklarationer av instanskonstruktorer, statiska konstruktorer, finalizers och operatorer är medlemsnamnet som används implementeringsberoende.

22.5.7 Kodanalysattribut

22.5.7.1 Allmänt

Attributen i det här avsnittet används för att ge ytterligare information för att stödja en kompilator som tillhandahåller null- och null-tillståndsdiagnostik (§8.9.5). En kompilator krävs inte för att utföra någon null-tillståndsdiagnostik. Förekomsten eller frånvaron av dessa attribut påverkar inte språket eller beteendet för ett program. En kompilator som inte tillhandahåller null-tillståndsdiagnostik ska läsa och ignorera förekomsten av dessa attribut. En kompilator som tillhandahåller null-tillståndsdiagnostik ska använda den betydelse som definieras i det här avsnittet för något av dessa attribut som används för att informera dess diagnostik.

Kodanalysattributen deklareras i namnområdet System.Diagnostics.CodeAnalysis.

Attribut Betydelse
AllowNull (§22.5.7.2) Ett argument som inte kan null-värdet kan vara null.
DisallowNull (§22.5.7.3) Ett null-argument får aldrig vara null.
MaybeNull (§22.5.7.6) Ett icke-nullbart returvärde kan vara null.
NotNull (§22.5.7.8) Ett null-returvärde blir aldrig null.
MaybeNullWhen (§22.5.7.7) Ett icke-nullbart argument kan vara null när metoden returnerar det angivna bool värdet.
NotNullWhen (§22.5.7.10) Ett null-argument är inte null när metoden returnerar det angivna bool värdet.
NotNullIfNotNull (§22.5.7.9) Ett returvärde är inte null om argumentet för den angivna parametern inte är null.
DoesNotReturn (§22.5.7.4) Den här metoden returnerar aldrig.
DoesNotReturnIf (§22.5.7.5) Den här metoden returnerar aldrig om den associerade bool parametern har det angivna värdet.

Följande avsnitt i §22.5.7.1 är villkorligt normativa.

22.5.7.2 Attributet AllowNull

Anger att ett null-värde tillåts som indata även om motsvarande typ inte tillåter det.

Exempel: Överväg följande läs-/skrivegenskap som aldrig returneras null eftersom den har ett rimligt standardvärde. En användare kan dock ge null till den angivna åtkomstorn för att ange egenskapen till det standardvärdet.

#nullable enable
public class X
{
    [AllowNull]
    public string ScreenName
    {
        get => _screenName;
        set => _screenName = value ?? GenerateRandomScreenName();
    }
    private string _screenName = GenerateRandomScreenName();
    private static string GenerateRandomScreenName() => ...;
}

Med tanke på följande användning av egenskapens uppsättningsåtkomstor

var v = new X();
v.ScreenName = null;   // may warn without attribute AllowNull

utan attributet kan en kompilator generera en varning eftersom egenskapen av typen icke-nullbar verkar vara inställd på ett null-värde. Förekomsten av attributet undertrycker varningen. slutexempel

22.5.7.3 Attributet DisallowNull

Anger att ett null-värde inte tillåts som indata även om motsvarande typ tillåter det.

Exempel: Tänk på följande egenskap där null är standardvärdet, men klienter kan bara ange värdet som ett värde som inte är null.

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

Get-accessorn kan returnera standardvärdet för null, så en kompilator kan varna för att den måste kontrolleras innan åtkomst. Dessutom varnar den anropare för att anropare inte uttryckligen ska ställa in värdet null, även om det kan vara null. slutexempel

22.5.7.4 Attributet DoesNotReturn

Anger att en viss metod aldrig returnerar.

Exempel: Tänk på följande:

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

Förekomsten av attributet hjälper en kompilator på flera olika sätt. Först kan en kompilator utfärda en varning om det finns en kodväg där metoden kan avslutas utan att utlösa ett undantag. För det andra kan en kompilator ignorera nullbara varningar i valfri kod efter ett anrop till metoden tills en lämplig catch-sats hittas. För det tredje påverkar inte den oåtkomliga koden några null-tillstånd.

Attributet ändrar inte nåbarheten (§13.2) eller en bestämd tilldelningsanalys (§9.4) baserat på förekomsten av detta attribut. Det används endast för att påverka nullabilitetsvarningar. slutexempel

22.5.7.5 Attributet DoesNotReturnIf

Anger att en viss metod aldrig returnerar om den associerade bool parametern har det angivna värdet.

Exempel: Tänk på följande:

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

slutexempel

22.5.7.6 Attributet MaybeNull

Anger att ett icke-nullbart returvärde kan vara null.

Exempel: Överväg följande generiska metod:

#nullable enable
public T? Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

Tanken med den här koden är att om T ersätts av stringblir T? en null-kommentar. Den här koden är dock inte laglig eftersom T den inte är begränsad till en referenstyp. Men att lägga till det här attributet löser problemet:

#nullable enable
[return: MaybeNull]
public T Find<T>(IEnumerable<T> sequence, Func<T, bool> predicate) { ... }

Attributet informerar anropare om att kontraktet innebär en icke-nullbar typ, men returvärdet kan faktiskt vara null. slutexempel

22.5.7.7 Attributet MaybeNullWhen

Anger att ett icke-nullbart argument kan vara null när metoden returnerar det angivna bool värdet. Detta liknar MaybeNull attributet (§22.5.7.6), men innehåller en parameter för det angivna returvärdet.

22.5.7.8 Attributet NotNull

Anger att ett null-värde aldrig null blir om metoden returnerar (i stället för att kasta).

Exempel: Tänk på följande:

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

När null-referenstyper är aktiverade kompileras metoden ThrowWhenNull utan varningar. När metoden returneras value är argumentet garanterat inte null. Det är dock acceptabelt att anropa ThrowWhenNull med en null-referens. slutexempel

22.5.7.9 Attributet NotNullIfNotNull

Anger att ett returvärde inte null är om argumentet för den angivna parametern inte nullär .

Exempel: Null-tillståndet för ett returvärde kan bero på nulltillståndet för ett eller flera argument. För att hjälpa en kompilators analys när en metod alltid returnerar ett värde som inte är null när vissa argument inte null kan attributet NotNullIfNotNull användas. Tänk på följande metod:

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

Om argumentet url inte nullär returneras null inte. När null-referenser är aktiverade fungerar signaturen korrekt, förutsatt att API:et aldrig accepterar ett null-argument. Men om argumentet kan vara null kan returvärdet också vara null. Om du vill uttrycka kontraktet korrekt kommenterar du den här metoden på följande sätt:

#nullable enable
[return: NotNullIfNotNull("url")]
string? GetTopLevelDomainFromFullUrl(string? url) { ... }

slutexempel

22.5.7.10 Attributet NotNullWhen

Anger att ett null-argument inte kommer att vara null när metoden returnerar det angivna bool värdet.

Exempel: Biblioteksmetoden String.IsNullOrEmpty(String) returnerar true när argumentet är null eller en tom sträng. Det är en form av null-check: Anropare behöver inte null-kontrollera argumentet om metoden returnerar false. Om du vill göra en metod som denna nullbar medveten gör du parametertypen till en nullbar referenstyp och lägger till attributet NotNullWhen:

#nullable enable
bool IsNullOrEmpty([NotNullWhen(false)] string? value) { ... }

slutexempel

22.6 Attribut för interoperation

För samarbete med andra språk kan en indexerare implementeras med hjälp av indexerade egenskaper. Om det inte finns något IndexerName attribut för en indexerare används namnet Item som standard. Med IndexerName attributet kan en utvecklare åsidosätta det här standardvärdet och ange ett annat namn.

Exempel: Som standard är Itemindexerarens namn . Detta kan åsidosättas enligt följande:

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

Indexerarens namn är TheItemnu .

slutexempel