Attributi vari interpretati dal compilatore C#

Gli attributi Conditional, Obsolete, AttributeUsage, AsyncMethodBuilder, InterpolatedStringHandler, ModuleInitializere Experimental possono essere applicati agli elementi nel codice. Aggiungono significato semantico a tali elementi. Il compilatore usa questi significati semantici per modificarne l'output e segnalare eventuali errori da parte degli sviluppatori che usano il codice.

Attributo Conditional

L'attributo Conditional rende l'esecuzione di un metodo dipendente da un identificatore di pre-elaborazione. L'attributo Conditional è un alias per ConditionalAttribute e può essere applicato a un metodo o a una classe Attribute.

Nell’esempio seguente, Conditional viene applicato a un metodo per attivare o disattivare la visualizzazione di informazioni di diagnostica specifiche del programma:

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

Se l'identificatore TRACE_ON non è definito, l'output di traccia non viene visualizzato. Esplorare se stessi nella finestra interattiva.

L'attributo Conditional viene usato spesso con l'identificatore DEBUG per abilitare funzioni di traccia e registrazione nelle compilazioni di debug ma non nelle build di rilascio, come illustrato nell'esempio seguente:

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

Quando viene chiamato un metodo contrassegnato come condizionale, la presenza o l'assenza del simbolo di pre-elaborazione specificato determina se il compilatore include o omette chiamate al metodo. Se il simbolo è definito la chiamata viene inclusa, in caso contrario viene omessa. Un metodo condizionale deve essere un metodo in una dichiarazione di classe o struct e deve avere un tipo restituito void. L'uso di Conditional rappresenta un'alternativa più efficiente, elegante e meno soggetta a errori rispetto all'inclusione di metodi nei blocchi #if…#endif.

Se un metodo ha più attributi Conditional, il compilatore include chiamate al metodo se vengono definiti uno o più simboli condizionali (i simboli vengono collegati logicamente usando l'operatore OR). Nell'esempio seguente la presenza di A o B comporta una chiamata al metodo:

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

Uso di Conditional con classi di attributi

L'attributo Conditional può essere applicato anche a una definizione di classe Attribute. Nell'esempio seguente, l'attributo personalizzato Documentation aggiunge informazioni ai metadati se DEBUG è definito.

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

Attributo Obsolete

L'attributo Obsolete contrassegna un elemento di codice come non più consigliato per l'uso. L'uso di un'entità contrassegnata come obsoleta genera un avviso o un errore. Obsolete è un attributo monouso e può essere applicato a qualsiasi entità che supporta gli attributi. Obsolete è un alias per ObsoleteAttribute.

Nell'esempio seguente l'attributo Obsolete viene applicato alla classe A e al metodo B.OldMethod. Poiché il secondo argomento del costruttore dell'attributo applicato a B.OldMethod è impostato su true questo metodo genera un errore del compilatore, mentre l'uso della classe A produce semplicemente un avviso. Tuttavia la chiamata di B.NewMethod non produrrà né un avviso né un errore. Ad esempio, se viene usato con le definizioni precedenti, il codice che segue genera due avvisi e un errore:


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

La stringa specificata come il primo argomento al costruttore dell'attributo viene inclusa nell'avviso o nell'errore visualizzato. Vengono generati due avvisi per la classe A: uno per la dichiarazione del riferimento alla classe e uno per il costruttore della classe. L'attributo Obsolete può essere usato senza argomenti, ma è consigliabile includere la spiegazione e l'indicazione degli elementi di codice da usare come alternativa.

In C# 10 è possibile usare l'interpolazione di stringhe costanti e l'operatore nameof per garantire che i nomi corrispondano:

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

    public void NewMethod() { }
}

Attributo Experimental

A partire da C# 12, i tipi, i metodi e gli assembly possono essere contrassegnati con il System.Diagnostics.CodeAnalysis.ExperimentalAttribute per indicare una funzionalità sperimentale. Il compilatore genera un avviso se si accede a un metodo o a un tipo annotato con il ExperimentalAttribute. Tutti i tipi dichiarati in un assembly o in un modulo contrassegnati con l'attributo Experimental sono sperimentali. Se si accede a uno di essi, il compilatore genera un avviso. È possibile disabilitare questi avvisi per la distribuzione pilota di una funzionalità sperimentale.

Avviso

Le funzionalità sperimentali sono soggette a modifiche. Le API possono cambiare o potrebbero essere rimosse negli aggiornamenti futuri. L'inclusione di funzionalità sperimentali è un modo per consentire agli autori di librerie di ottenere feedback su idee e concetti per lo sviluppo futuro. Prestare particolare attenzione quando si usa qualsiasi funzionalità contrassegnata come sperimentale.

Per altre informazioni sull'attributo Experimental, vedere la specifica di funzionalità .

Attributo SetsRequiredMembers

L'attributo SetsRequiredMembers indica al compilatore che un costruttore imposta tutti i membri required in tale classe o struct. Il compilatore presuppone che qualsiasi costruttore con l'attributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute inizializzi tutti i membri required. Qualsiasi codice che richiama tale costruttore non richiede inizializzatori di oggetto per impostare i membri necessari. L'aggiunta dell'attributo SetsRequiredMembers è utile principalmente per i record posizionali e i costruttori primari.

Attributo AttributeUsage

L'attributo AttributeUsage determina come usare una classe di attributi personalizzata. AttributeUsageAttribute è un attributo che si applica alle definizioni di attributi personalizzati. L'attributo AttributeUsage consente di controllare:

  • Elementi del programma a cui l'attributo può essere applicato. Se non se ne limita l'utilizzo, un attributo può essere applicato a uno qualsiasi degli elementi del programma seguenti:
    • Assemblaggio
    • Modulo
    • Campo
    • Evento
    • metodo
    • Param
    • Proprietà
    • Restituzione
    • Type
  • Se un attributo può essere applicato più volte a un singolo elemento del programma.
  • Indica se le classi derivate ereditano attributi.

Le impostazioni predefinite sono simili all'esempio seguente quando vengono applicate in modo esplicito:

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

In questo esempio la classe NewAttribute può essere applicata a qualsiasi elemento del programma supportato, ma solo una volta a ogni entità. Le classi derivate ereditano l'attributo applicato a una classe base.

Gli argomenti AllowMultiple e Inherited sono facoltativi, quindi il codice seguente ha lo stesso effetto:

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

Il primo argomento AttributeUsageAttribute deve consistere di uno o più elementi dell'enumerazione AttributeTargets. Più tipi di destinazione possono essere collegati con l'operatore OR, nel modo illustrato nell'esempio seguente:

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

Gli attributi possono essere applicati alla proprietà o al campo sottostante per una proprietà diimplementazione automatica. L'attributo si applica alla proprietà, a meno che non si specifichi l'identificatore field per l'attributo. Entrambe le situazioni sono illustrate nell'esempio seguente:

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

Se l'argomento AllowMultiple è true, l'attributo restituito può essere applicato più volte a una singola entità, come illustrato nell'esempio seguente:

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

[MultiUse]
[MultiUse]
class Class1 { }

[MultiUse, MultiUse]
class Class2 { }

In questo caso, MultiUseAttribute può essere applicato più volte perché AllowMultiple è impostato su true. Entrambi i formati illustrati per applicare più attributi sono validi.

Se Inherited è false, le classi derivate non ereditano l'attributo da una classe base con attributi. Ad esempio:

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

[NonInherited]
class BClass { }

class DClass : BClass { }

In questo caso NonInheritedAttribute non viene applicato a DClass attraverso l'ereditarietà.

È anche possibile usare queste parole chiave per specificare dove applicare un attributo. Ad esempio, è possibile usare l'identificatore di field: per aggiungere un attributo al campo sottostante di una proprietà implementata automaticamente. In alternativa, è possibile usare l'identificatore field:, property: o param: per applicare un attributo a uno qualsiasi degli elementi generati da un record posizionale. Per un esempio, vedere Sintassi posizionale per la definizione di proprietà.

Attributo AsyncMethodBuilder

Aggiungere l'attributo System.Runtime.CompilerServices.AsyncMethodBuilderAttribute a un tipo che può essere un tipo restituito asincrono. L'attributo specifica il tipo che compila l'implementazione del metodo asincrono quando il tipo specificato viene restituito da un metodo asincrono. L'attributo AsyncMethodBuilder può essere applicato a un tipo che:

Il costruttore dell'attributo AsyncMethodBuilder specifica il tipo del generatore associato. Il generatore deve implementare i membri accessibili seguenti:

  • Metodo Create() statico che restituisce il tipo del generatore.

  • Proprietà Task leggibile che restituisce il tipo restituito asincrono.

  • Metodo void SetException(Exception) che imposta l'eccezione quando si verifica un errore di un'attività.

  • Metodo void SetResult() o void SetResult(T result) che contrassegna l'attività come completata e, facoltativamente, imposta il risultato dell'attività

  • Metodo Start con la firma API seguente:

    void Start<TStateMachine>(ref TStateMachine stateMachine)
              where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • Metodo AwaitOnCompleted con la firma seguente:

    public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
        where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion
        where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
    
  • Metodo AwaitUnsafeOnCompleted con la firma seguente:

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

Per informazioni sui generatori di metodi asincroni, vedere i generatori seguenti forniti da .NET:

In C# 10 e versioni successive, l'attributo AsyncMethodBuilder può essere applicato a un metodo asincrono per eseguire l'override del generatore per tale tipo.

Attributi InterpolatedStringHandler e InterpolatedStringHandlerArguments

A partire da C# 10, questi attributi vengono usati per specificare che un tipo è un gestore di stringhe interpolato . La libreria .NET 6 include già System.Runtime.CompilerServices.DefaultInterpolatedStringHandler per gli scenari in cui si usa una stringa interpolata come argomento per un parametro string . Potrebbero essere presenti altre istanze in cui si vuole controllare la modalità di elaborazione delle stringhe interpolate. Applicare il System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute al tipo che implementa il gestore. Applicare il System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute ai parametri del costruttore di quel tipo.

Per altre informazioni sulla creazione di un gestore di stringhe interpolate, vedere la specifica della funzionalità C# 10 per miglioramenti delle stringhe interpolate.

Attributo ModuleInitializer

L'attributo ModuleInitializer contrassegna un metodo che il runtime chiama quando l'assembly viene caricato. ModuleInitializer è un alias per ModuleInitializerAttribute.

L'attributo ModuleInitializer può essere applicato solo a un metodo che:

  • è statico.
  • È senza parametri.
  • Restituisce void.
  • È accessibile dal modulo contenitore, ovvero internal o public.
  • Non è un metodo generico.
  • Non è contenuto in una classe generica.
  • Non è una funzione locale.

L'attributo ModuleInitializer può essere applicato a più metodi. In tal caso, l'ordine in cui il runtime li chiama è deterministico ma non specificato.

Nell'esempio seguente viene illustrato l'uso di più metodi di inizializzatore del modulo. I metodi Init1 e Init2 vengono eseguiti prima di Maine ognuno aggiunge una stringa alla proprietà Text. Pertanto, quando Main viene eseguita, la proprietà Text contiene già stringhe di entrambi i metodi di inizializzatore.

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

I generatori di codice sorgente talvolta devono generare codice di inizializzazione. Gli inizializzatori di modulo forniscono una posizione standard per il codice. Nella maggior parte degli altri casi, è consigliabile scrivere un costruttore statico anziché un inizializzatore di modulo.

Attributo SkipLocalsInit

L'attributo SkipLocalsInit impedisce al compilatore di impostare il flag .locals init durante l'emissione di metadati. L'attributo SkipLocalsInit è un attributo a uso singolo e può essere applicato a un metodo, a una proprietà, a una classe, a uno struct, a un'interfaccia o a un modulo, ma non a un assembly. SkipLocalsInit è un alias per SkipLocalsInitAttribute.

Il .locals init flag fa sì che CLR inizializzi tutte le variabili locali dichiarate in un metodo sui valori predefiniti. Poiché il compilatore assicura inoltre di non usare mai una variabile prima di assegnarvi un valore, .locals init in genere non è necessario. Tuttavia, l'inizializzazione zero aggiuntiva potrebbe avere un impatto misurabile sulle prestazioni in alcuni scenari, ad esempio quando si usa stackalloc per allocare una matrice nello stack. In questi casi, è possibile aggiungere l'attributo SkipLocalsInit. Se applicato direttamente a un metodo, l'attributo influisce sul metodo e su tutte le relative funzioni annidate, incluse le espressioni lambda e le funzioni locali. Se applicato a un tipo o a un modulo, influisce su tutti i metodi annidati all'interno. Questo attributo non influisce sui metodi astratti, ma influisce sul codice generato per l'implementazione.

Questo attributo richiede l'opzione del compilatore AllowUnsafeBlocks. Questo requisito segnala che in alcuni casi il codice potrebbe visualizzare la memoria non assegnata, ad esempio la lettura dalla memoria allocata allo stack non inizializzata.

Nell'esempio seguente viene illustrato l'effetto dell'SkipLocalsInitattributo su un metodo che usa stackalloc. Il metodo visualizza qualsiasi elemento in memoria quando è stata allocata la matrice di numeri interi.

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

Per provare il codice manualmente, impostare l'opzione del compilatore AllowUnsafeBlocks nel file con estensione csproj :

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

Vedi anche