Diverse kenmerken die worden geïnterpreteerd door de C#-compiler

De kenmerken Conditional, , Obsolete, AttributeUsage, AsyncMethodBuilder, InterpolatedStringHandleren ModuleInitializerExperimental kunnen worden toegepast op elementen in uw code. Ze voegen semantische betekenis toe aan deze elementen. De compiler gebruikt deze semantische betekenissen om de uitvoer te wijzigen en mogelijke fouten te rapporteren door ontwikkelaars die uw code gebruiken.

Conditional-kenmerk

Het Conditional kenmerk maakt de uitvoering van een methode afhankelijk van een voorverwerkings-id. Het Conditional kenmerk is een alias voor ConditionalAttributeen kan worden toegepast op een methode of een kenmerkklasse.

In het volgende voorbeeld Conditional wordt toegepast op een methode om de weergave van programmaspecifieke diagnostische gegevens in of uit te schakelen:

#define TRACE_ON
using System.Diagnostics;

namespace AttributeExamples;

public class Trace
{
    [Conditional("TRACE_ON")]
    public static void Msg(string msg)
    {
        Console.WriteLine(msg);
    }
}

public class TraceExample
{
    public static void Main()
    {
        Trace.Msg("Now in Main...");
        Console.WriteLine("Done.");
    }
}

Als de TRACE_ON id niet is gedefinieerd, wordt de traceringsuitvoer niet weergegeven. Verken jezelf in het interactieve venster.

Het Conditional kenmerk wordt vaak gebruikt met de DEBUG id om tracerings- en logboekregistratiefuncties in te schakelen voor foutopsporingsversies, maar niet in release-builds, zoals wordt weergegeven in het volgende voorbeeld:

[Conditional("DEBUG")]
static void DebugMethod()
{
}

Wanneer een methode die is gemarkeerd als voorwaarde wordt aangeroepen, bepaalt de aanwezigheid of afwezigheid van het opgegeven voorverwerkingssymbool of de compiler aanroepen naar de methode bevat of weglaat. Als het symbool is gedefinieerd, wordt de aanroep opgenomen; anders wordt de aanroep weggelaten. Een voorwaardelijke methode moet een methode zijn in een klasse- of structdeclaratie en moet een void retourtype hebben. Het gebruik Conditional is schoner, eleganter en minder foutgevoelig dan het insluiten van methoden in #if…#endif blokken.

Als een methode meerdere Conditional kenmerken heeft, bevat compiler aanroepen naar de methode als een of meer voorwaardelijke symbolen zijn gedefinieerd (de symbolen zijn logisch aan elkaar gekoppeld met behulp van de OPERATOR OR). In het volgende voorbeeld wordt de aanwezigheid van A een van beide of B resultaten in een methode-aanroep weergegeven:

[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
    // ...
}

Gebruiken Conditional met kenmerkklassen

Het Conditional kenmerk kan ook worden toegepast op een kenmerkklassedefinitie. In het volgende voorbeeld voegt het aangepaste kenmerk Documentation informatie toe aan de metagegevens als DEBUG deze is gedefinieerd.

[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
    string text;

    public DocumentationAttribute(string text)
    {
        this.text = text;
    }
}

class SampleClass
{
    // This attribute will only be included if DEBUG is defined.
    [Documentation("This method displays an integer.")]
    static void DoWork(int i)
    {
        System.Console.WriteLine(i.ToString());
    }
}

Obsolete-kenmerk

Het Obsolete kenmerk markeert een code-element, omdat dit niet meer wordt aanbevolen voor gebruik. Het gebruik van een entiteit die is gemarkeerd als verouderd, genereert een waarschuwing of een fout. Het Obsolete kenmerk is een kenmerk voor eenmalig gebruik en kan worden toegepast op elke entiteit die kenmerken toestaat. Obsolete is een alias voor ObsoleteAttribute.

In het volgende voorbeeld wordt het Obsolete kenmerk toegepast op klasse A en methode B.OldMethod. Omdat het tweede argument van de kenmerkconstructor waarop is toegepast B.OldMethod is ingesteld true, veroorzaakt deze methode een compilerfout, terwijl het gebruik van klasse A een waarschuwing produceert. Aanroepen B.NewMethodproduceert echter geen waarschuwing of fout. Wanneer u deze bijvoorbeeld gebruikt met de vorige definities, genereert de volgende code twee waarschuwingen en één fout:


namespace AttributeExamples
{
    [Obsolete("use class B")]
    public class A
    {
        public void Method() { }
    }

    public class B
    {
        [Obsolete("use NewMethod", true)]
        public void OldMethod() { }

        public void NewMethod() { }
    }

    public static class ObsoleteProgram
    {
        public static void Main()
        {
            // Generates 2 warnings:
            A a = new A();

            // Generate no errors or warnings:
            B b = new B();
            b.NewMethod();

            // Generates an error, compilation fails.
            // b.OldMethod();
        }
    }
}

De tekenreeks die wordt opgegeven als het eerste argument voor de kenmerkconstructor, wordt weergegeven als onderdeel van de waarschuwing of fout. Er worden twee waarschuwingen voor klasse A gegenereerd: één voor de declaratie van de klassereferentie en één voor de klasseconstructor. Het Obsolete kenmerk kan zonder argumenten worden gebruikt, maar ook een uitleg over wat u in plaats daarvan moet gebruiken, wordt aanbevolen.

In C# 10 kunt u constante tekenreeksinterpolatie en de nameof operator gebruiken om ervoor te zorgen dat de namen overeenkomen:

public class B
{
    [Obsolete($"use {nameof(NewMethod)} instead", true)]
    public void OldMethod() { }

    public void NewMethod() { }
}

Experimental-kenmerk

Vanaf C# 12 kunnen typen, methoden en assembly's worden gemarkeerd met de System.Diagnostics.CodeAnalysis.ExperimentalAttribute functie om een experimentele functie aan te geven. De compiler geeft een waarschuwing uit als u toegang krijgt tot een methode of een type met aantekeningen bij de ExperimentalAttribute. Alle typen die zijn gedeclareerd in een assembly of module die zijn gemarkeerd met het Experimental kenmerk, zijn experimenteel. De compiler geeft een waarschuwing uit als u er toegang toe hebt. U kunt deze waarschuwingen uitschakelen om een experimentele functie uit te voeren.

Waarschuwing

Experimentele functies zijn onderhevig aan wijzigingen. De API's kunnen worden gewijzigd of worden verwijderd in toekomstige updates. Het opnemen van experimentele functies is een manier voor bibliotheekauteurs om feedback te krijgen over ideeën en concepten voor toekomstige ontwikkeling. Wees uiterst voorzichtig wanneer u een functie gebruikt die is gemarkeerd als experimenteel.

Meer informatie over het Experimental kenmerk vindt u in de functiespecificatie.

SetsRequiredMembers-kenmerk

Het SetsRequiredMembers kenmerk informeert de compiler dat een constructor alle required leden in die klasse of struct instelt. De compiler gaat ervan uit dat een constructor met het System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute kenmerk alle required leden initialiseert. Elke code die een dergelijke constructor aanroept, heeft geen object-initialisatiefuncties nodig om vereiste leden in te stellen. Het toevoegen van het SetsRequiredMembers kenmerk is voornamelijk handig voor positionele records en primaire constructors.

AttributeUsage-kenmerk

Het AttributeUsage kenmerk bepaalt hoe een aangepaste kenmerkklasse kan worden gebruikt. AttributeUsageAttribute is een kenmerk dat u toepast op aangepaste kenmerkdefinities. Met AttributeUsage het kenmerk kunt u het volgende beheren:

  • Op welke programma-elementen het kenmerk kan worden toegepast. Tenzij u het gebruik ervan beperkt, kan een kenmerk worden toegepast op een van de volgende programma-elementen:
    • Assembly
    • Module
    • Veld
    • Gebeurtenis
    • Wijze
    • Param
    • Eigenschappen
    • Retourneren
    • Type
  • Of een kenmerk meerdere keren kan worden toegepast op één programma-element.
  • Of afgeleide klassen kenmerken overnemen.

De standaardinstellingen zien er als volgt uit wanneer deze expliciet worden toegepast:

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

In dit voorbeeld kan de NewAttribute klasse worden toegepast op elk ondersteund programma-element. Maar het kan slechts één keer worden toegepast op elke entiteit. Afgeleide klassen nemen het kenmerk over dat is toegepast op een basisklasse.

De AllowMultiple en Inherited argumenten zijn optioneel, dus de volgende code heeft hetzelfde effect:

[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }

Het eerste AttributeUsageAttribute argument moet een of meer elementen van de AttributeTargets opsomming zijn. Meerdere doeltypen kunnen worden gekoppeld aan de OPERATOR OF, zoals in het volgende voorbeeld:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }

Kenmerken kunnen worden toegepast op de eigenschap of het backing-veld voor een automatisch geïmplementeerde eigenschap. Het kenmerk is van toepassing op de eigenschap, tenzij u de field aanduiding voor het kenmerk opgeeft. Beide worden weergegeven in het volgende voorbeeld:

class MyClass
{
    // Attribute attached to property:
    [NewPropertyOrField]
    public string Name { get; set; } = string.Empty;

    // Attribute attached to backing field:
    [field: NewPropertyOrField]
    public string Description { get; set; } = string.Empty;
}

Als het AllowMultiple argument is true, kan het resulterende kenmerk meerdere keren worden toegepast op één entiteit, zoals wordt weergegeven in het volgende voorbeeld:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }

[MultiUse]
[MultiUse]
class Class1 { }

[MultiUse, MultiUse]
class Class2 { }

In dit geval MultiUseAttribute kan herhaaldelijk worden toegepast omdat AllowMultiple deze is ingesteld op true. Beide indelingen die worden weergegeven voor het toepassen van meerdere kenmerken, zijn geldig.

Als Inherited dat het is false, nemen afgeleide klassen het kenmerk niet over van een toegeschreven basisklasse. Voorbeeld:

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }

[NonInherited]
class BClass { }

class DClass : BClass { }

In dit geval NonInheritedAttribute wordt dit niet toegepast DClass via overname.

U kunt deze trefwoorden ook gebruiken om op te geven waar een kenmerk moet worden toegepast. U kunt bijvoorbeeld de field: aanduiding gebruiken om een kenmerk toe te voegen aan het backingveld van een automatisch geïmplementeerde eigenschap. U kunt ook de field:of property:param: aanduiding gebruiken om een kenmerk toe te passen op een van de elementen die zijn gegenereerd op basis van een positionele record. Zie Positionele syntaxis voor eigenschapsdefinitie voor een voorbeeld.

AsyncMethodBuilder-kenmerk

U voegt het System.Runtime.CompilerServices.AsyncMethodBuilderAttribute kenmerk toe aan een type dat een asynchroon retourtype kan zijn. Het kenmerk geeft het type op dat de implementatie van de asynchrone methode bouwt wanneer het opgegeven type wordt geretourneerd vanuit een asynchrone methode. Het AsyncMethodBuilder kenmerk kan worden toegepast op een type dat:

De constructor voor het AsyncMethodBuilder kenmerk geeft het type van de bijbehorende opbouwfunctie aan. De opbouwfunctie moet de volgende toegankelijke leden implementeren:

  • Een statische Create() methode die het type opbouwfunctie retourneert.

  • Een leesbare Task eigenschap die het asynchrone retourtype retourneert.

  • Een void SetException(Exception) methode waarmee de uitzondering wordt ingesteld wanneer een taak fouten veroorzaakt.

  • Een void SetResult() of void SetResult(T result) methode waarmee de taak wordt gemarkeerd als voltooid en optioneel het resultaat van de taak wordt ingesteld

  • Een Start methode met de volgende API-handtekening:

    void Start<TStateMachine>(ref TStateMachine stateMachine)
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • Een AwaitOnCompleted methode met de volgende handtekening:

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • Een AwaitUnsafeOnCompleted methode met de volgende handtekening:

          public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
              where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    

U vindt meer informatie over asynchrone opbouwfuncties voor methoden door te lezen over de volgende opbouwfuncties die door .NET worden geleverd:

In C# 10 en hoger kan het AsyncMethodBuilder kenmerk worden toegepast op een asynchrone methode om de opbouwfunctie voor dat type te overschrijven.

InterpolatedStringHandler en InterpolatedStringHandlerArguments kenmerken

Vanaf C# 10 gebruikt u deze kenmerken om op te geven dat een type een geïnterpoleerde tekenreekshandler is. De .NET 6-bibliotheek bevat System.Runtime.CompilerServices.DefaultInterpolatedStringHandler al voor scenario's waarin u een geïnterpoleerde tekenreeks gebruikt als het argument voor een string parameter. Mogelijk hebt u andere exemplaren waarin u wilt bepalen hoe geïnterpoleerde tekenreeksen worden verwerkt. U past het System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute toe op het type dat uw handler implementeert. U past de System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute parameters van de constructor van dat type toe.

Meer informatie over het bouwen van een geïnterpoleerde tekenreekshandler vindt u in de C# 10-functiespecificatie voor geïnterpoleerde tekenreeksverbeteringen.

ModuleInitializer-kenmerk

Het ModuleInitializer kenmerk markeert een methode die door de runtime wordt aangeroepen wanneer de assembly wordt geladen. ModuleInitializer is een alias voor ModuleInitializerAttribute.

Het ModuleInitializer kenmerk kan alleen worden toegepast op een methode die:

  • Is statisch.
  • Is parameterloos.
  • Retourneert void.
  • Is toegankelijk vanuit de betreffende module, dat wil internal wel.public
  • Is geen algemene methode.
  • Is niet opgenomen in een algemene klasse.
  • Is geen lokale functie.

Het ModuleInitializer kenmerk kan worden toegepast op meerdere methoden. In dat geval is de volgorde waarin de runtime deze aanroept deterministisch, maar niet opgegeven.

In het volgende voorbeeld ziet u hoe u meerdere initialisatiemethoden voor modules gebruikt. De Init1 en Init2 methoden worden eerder Mainuitgevoerd en elke methode voegt een tekenreeks toe aan de Text eigenschap. Dus wanneer Main de eigenschap wordt uitgevoerd, heeft de Text eigenschap al tekenreeksen van beide initialisatiemethoden.

using System;

internal class ModuleInitializerExampleMain
{
    public static void Main()
    {
        Console.WriteLine(ModuleInitializerExampleModule.Text);
        //output: Hello from Init1! Hello from Init2!
    }
}
using System.Runtime.CompilerServices;

internal class ModuleInitializerExampleModule
{
    public static string? Text { get; set; }

    [ModuleInitializer]
    public static void Init1()
    {
        Text += "Hello from Init1! ";
    }

    [ModuleInitializer]
    public static void Init2()
    {
        Text += "Hello from Init2! ";
    }
}

Broncodegeneratoren moeten soms initialisatiecode genereren. Module-initializers bieden een standaardplaats voor die code. In de meeste andere gevallen moet u een statische constructor schrijven in plaats van een module-initialisatiefunctie.

SkipLocalsInit-kenmerk

Het SkipLocalsInit kenmerk voorkomt dat de compiler de .locals init vlag instelt bij het verzenden naar metagegevens. Het SkipLocalsInit kenmerk is een kenmerk voor eenmalig gebruik en kan worden toegepast op een methode, een eigenschap, een klasse, een struct, een interface of een module, maar niet op een assembly. SkipLocalsInit is een alias voor SkipLocalsInitAttribute.

De .locals init vlag zorgt ervoor dat de CLR alle lokale variabelen initialiseert die zijn gedeclareerd in een methode naar de standaardwaarden. Omdat de compiler er ook voor zorgt dat u nooit een variabele gebruikt voordat u er een waarde aan toewijst, .locals init is dit doorgaans niet nodig. De extra nul-initialisatie kan echter meetbare invloed hebben op de prestaties in sommige scenario's, zoals wanneer u stackalloc gebruikt om een matrix toe te wijzen aan de stack. In die gevallen kunt u het SkipLocalsInit kenmerk toevoegen. Als het kenmerk rechtstreeks op een methode wordt toegepast, is dit van invloed op die methode en alle geneste functies, waaronder lambdas en lokale functies. Als dit wordt toegepast op een type of module, is dit van invloed op alle methoden die binnen zijn genest. Dit kenmerk heeft geen invloed op abstracte methoden, maar heeft wel invloed op de code die is gegenereerd voor de implementatie.

Voor dit kenmerk is de optie AllowUnsafeBlocks-compiler vereist. Deze vereiste geeft aan dat code in sommige gevallen niet-toegewezen geheugen kan weergeven (bijvoorbeeld lezen uit niet-geïnitialiseerd geheugen dat is toegewezen aan stack).

In het volgende voorbeeld ziet u het effect van SkipLocalsInit het kenmerk op een methode die gebruikmaakt van stackalloc. De methode geeft wat zich in het geheugen bevond toen de matrix met gehele getallen werd toegewezen.

[SkipLocalsInit]
static void ReadUninitializedMemory()
{
    Span<int> numbers = stackalloc int[120];
    for (int i = 0; i < 120; i++)
    {
        Console.WriteLine(numbers[i]);
    }
}
// output depends on initial contents of memory, for example:
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
// Remaining rows omitted for brevity.

Als u deze code zelf wilt proberen, stelt u de AllowUnsafeBlocks compileroptie in uw .csproj-bestand in:

<PropertyGroup>
  ...
  <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

Zie ook