Attributs divers interprétés par le compilateur C#
Il existe plusieurs attributs que vous pouvez appliquer à des éléments de votre code qui y ajoutent une signification sémantique :
Conditional
: rendre l’exécution d’une méthode dépendante d’un identificateur de prétraitement.Obsolete
: marquer un type ou membre pour une suppression possible (possible).AttributeUsage
: déclarer les éléments d’un langage où vous pouvez appliquer un attribut.AsyncMethodBuilder
: déclarer un type de générateur de méthode asynchrone.InterpolatedStringHandler
: définir un générateur de chaîne interpolée pour un scénario connu.ModuleInitializer
: déclarer une méthode qui initialise un module.SkipLocalsInit
: omettre le code qui initialise un stockage de variable local sur 0.UnscopedRef
: déclarer qu’une variableref
normalement interprétée commescoped
doit être traitée comme non délimitée.OverloadResolutionPriority
: Ajoutez un attribut de départage pour influencer la résolution de surcharge en cas de surcharges potentiellement ambiguës.Experimental
: marquer un type ou membre comme expérimental.
Le compilateur utilise ces significations sémantiques pour modifier sa sortie et signaler les erreurs possibles par les développeurs qui utilisent votre code.
Attribut Conditional
Avec l’attribut Conditional
, l’exécution d’une méthode dépend d’un identificateur de prétraitement. L’attribut Conditional
est un alias pour ConditionalAttribute, et peut être appliqué à une méthode ou une classe d’attributs.
Dans l’exemple suivant, Conditional
est appliqué à une méthode pour activer ou désactiver l’affichage d’informations de diagnostic spécifiques au programme :
#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.");
}
}
Si l’identificateur TRACE_ON
n’est pas défini, la sortie de trace n’est pas affichée. Explorez par vous-même dans la fenêtre interactive.
L’attribut Conditional
est souvent utilisé avec l’identificateur DEBUG
pour activer des fonctionnalités de traçage et de journalisation pour les builds de débogage, mais pas pour les versions Release, comme le montre l’exemple suivant :
[Conditional("DEBUG")]
static void DebugMethod()
{
}
Quand une méthode marquée comme conditionnelle est appelée, la présence ou l’absence du symbole de prétraitement spécifié détermine si le compilateur inclut ou omet l’appel à la méthode. Si le symbole est défini, l’appel est inclus ; sinon, il est omis. Une méthode conditionnelle doit figurer dans une déclaration de classe ou de struct et doit avoir une type de retour void
. Conditional
offre une solution à la fois plus efficace, plus élégante et moins sujette aux erreurs que les méthodes englobantes contenues dans les blocs #if…#endif
.
Si une méthode a plusieurs attributs Conditional
, le compilateur inclut des appels à la méthode si un ou plusieurs symboles conditionnels sont définis (les symboles sont liés logiquement à l’aide de l’opérateur OR). Dans l’exemple suivant, la présence de résultats A
ou B
entraîne un appel de méthode :
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}
Utilisation de Conditional
avec des classes d’attributs
L’attribut Conditional
peut également être appliqué à une définition de classe d’attributs. Dans l’exemple suivant, l’attribut personnalisé Documentation
ajoute des informations aux métadonnées si DEBUG
est défini.
[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());
}
}
Attribut Obsolete
L'attribut Obsolete
marque un élément de code comme n’est plus recommandé pour une utilisation. L’utilisation d’une entité marquée comme obsolète génère un avertissement ou une erreur. Obsolete
est un attribut à usage unique et peut être appliqué à toute entité qui autorise des attributs. Obsolete
est un alias pour ObsoleteAttribute.
Dans l’exemple suivant, l’attribut Obsolete
est appliqué à la classe A
et à la méthode B.OldMethod
. Comme le deuxième argument du constructeur d’attribut appliqué à B.OldMethod
a la valeur true
, l’utilisation de cette méthode entraîne une erreur du compilateur, alors que l’utilisation de la classe A
n’entraîne qu’un avertissement. Toutefois, l’appel de B.NewMethod
ne produit aucun avertissement ni aucune erreur. Par exemple, utilisé avec les définitions précédentes, le code suivant génère deux avertissements et une erreur :
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 chaîne fournie comme premier argument au constructeur d’attribut est affichée dans l’avertissement ou dans l’erreur. Deux avertissements pour la classe A
sont générés : un pour la déclaration de la référence de classe et un pour le constructeur de classe. L’attribut Obsolete
peut être utilisé sans arguments, mais il est recommandé d’inclure une explication indiquant quoi utiliser en remplacement.
En C# 10, vous pouvez utiliser l’interpolation de chaîne constante et l’opérateur nameof
pour vous assurer que les noms correspondent :
public class B
{
[Obsolete($"use {nameof(NewMethod)} instead", true)]
public void OldMethod() { }
public void NewMethod() { }
}
Attribut Experimental
À compter de C# 12, les types, les méthodes et les assemblys peuvent être marqués avec System.Diagnostics.CodeAnalysis.ExperimentalAttribute pour indiquer une fonctionnalité expérimentale. Le compilateur émet un avertissement si vous accédez à une méthode ou un type annoté de ExperimentalAttribute. Tous les types déclarés dans un assembly ou un module marqués avec l’attribut Experimental
sont expérimentaux. Le compilateur émet un avertissement si vous accédez à l’un d’entre eux. Vous pouvez désactiver ces avertissements pour piloter une fonctionnalité expérimentale.
Avertissement
Les fonctionnalités expérimentales sont soumises à des modifications. Les API peuvent changer, ou elles peuvent être supprimées au cours des prochaines mises à jour. L’inclusion de fonctionnalités expérimentales est un moyen pour les auteurs de bibliothèques d’obtenir des commentaires sur les idées et les concepts pour le développement futur. Utilisez une prudence extrême lors de l’utilisation d’une fonctionnalité marquée comme expérimentale.
Vous pouvez en savoir plus sur l’attribut Experimental
dans la spécification de fonctionnalité.
Attribut SetsRequiredMembers
L’attribut SetsRequiredMembers
informe le compilateur qu’un constructeur définit tous les membres required
de cette classe ou de ce struct. Le compilateur suppose que tout constructeur avec l’attribut System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute initialise tous les membres required
. Tout code qui appelle un tel constructeur n’a pas besoin d’initialiseurs d’objet pour définir les membres requis. L’ajout de l’attribut SetsRequiredMembers
est principalement utile pour les enregistrements positionnels et les constructeurs principaux.
Attribut AttributeUsage
L’attribut AttributeUsage
détermine de quelle manière une classe d’attributs personnalisés peut être utilisée. AttributeUsageAttribute est un attribut que vous appliquez à des définitions d’attribut personnalisé. L’attribut AttributeUsage
vous permet de contrôler :
- À quels éléments de programme les attributs peuvent être appliqués. Sauf si vous en limitez l’utilisation, un attribut peut être appliqué aux éléments de programme suivants :
- Assembly
- Module
- Champ
- Événement
- Method
- Paramètre
- Propriété
- Renvoie
- Type
- Si un attribut peut être appliqué plusieurs fois à un même élément de programme.
- Si les classes dérivées héritent des attributs.
L’exemple suivant montre des paramètres par défaut quand ils sont appliqués explicitement :
[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : Attribute { }
Dans cet exemple, la classe NewAttribute
peut être appliquée à n’importe quel élément de programme pris en charge. Elle ne peut cependant être appliquée qu’une seule fois à chaque entité. Les classes dérivées héritent de l’attribut quand il est appliqué à une classe de base.
Les arguments AllowMultiple et Inherited étant facultatifs, le code suivant a le même effet :
[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }
Le premier argument AttributeUsageAttribute doit correspondre à un ou plusieurs éléments de l’énumération AttributeTargets. Vous pouvez lier ensemble plusieurs types de cibles avec l’opérateur OR, comme dans l’exemple suivant :
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }
Les attributs peuvent être appliqués à la propriété ou au champ de stockage d’une propriété implémentée automatiquement. L’attribut s’applique à la propriété, sauf si vous spécifiez le spécificateur field
sur l’attribut. Les deux cas sont illustrés dans l’exemple suivant :
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;
}
Si l’argument AllowMultiple est défini sur true
, l’attribut résultant peut être appliqué plusieurs fois à une même entité, comme illustré dans l’exemple suivant :
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }
[MultiUse]
[MultiUse]
class Class1 { }
[MultiUse, MultiUse]
class Class2 { }
Dans ce cas, MultiUseAttribute
peut être appliqué à plusieurs reprises, car AllowMultiple
est défini sur true
. Les deux formats indiqués pour appliquer plusieurs attributs sont valides.
Si Inherited est false
, les classes dérivées n’héritent pas de l’attribut d’une classe avec attributs. Par exemple :
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }
[NonInherited]
class BClass { }
class DClass : BClass { }
Dans ce cas, NonInheritedAttribute
n’est pas appliqué à DClass
via l’héritage.
Vous pouvez également utiliser ces mots clés pour spécifier où un attribut doit être appliqué. Par exemple, vous pouvez utiliser le field:
spécificateur pour ajouter un attribut au champ de stockage d’une propriété implémentée automatiquement. Vous pouvez également utiliser le spécificateur field:
, property:
ou param:
pour appliquer un attribut à l’un des éléments générés à partir d’un enregistrement positionnel. Pour obtenir un exemple, consultez Syntaxe positionnelle pour la définition de propriété.
Attribut AsyncMethodBuilder
Vous ajoutez l’attribut System.Runtime.CompilerServices.AsyncMethodBuilderAttribute à un type qui peut être un type de retour asynchrone. L’attribut spécifie le type qui génère l’implémentation de la méthode asynchrone lorsque le type spécifié est retourné à partir d’une méthode asynchrone. L’attribut AsyncMethodBuilder
peut être appliqué à un type qui :
- A une méthode accessible
GetAwaiter
. - L’objet retourné par la méthode
GetAwaiter
implémente l’interface System.Runtime.CompilerServices.ICriticalNotifyCompletion.
Le constructeur de l’attribut AsyncMethodBuilder
spécifie le type du générateur associé. Le générateur doit implémenter les membres accessibles suivants :
Méthode statique
Create()
qui retourne le type du générateur.Propriété lisible
Task
qui renvoie le type de retour asynchrone.Méthode
void SetException(Exception)
qui définit l’exception en cas d’erreur d’une tâche.Méthode
void SetResult()
ouvoid SetResult(T result)
qui marque la tâche comme terminée et définit éventuellement le résultat de la tâcheMéthode
Start
avec la signature d’API suivante :void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Méthode
AwaitOnCompleted
avec la signature suivante :public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Méthode
AwaitUnsafeOnCompleted
avec la signature suivante :public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
Vous pouvez en savoir plus sur les générateurs de méthodes asynchrones en lisant les générateurs suivants fournis par .NET :
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
Dans C# 10 et versions ultérieures, l’attribut AsyncMethodBuilder
peut être appliqué à une méthode asynchrone pour remplacer le générateur pour ce type.
Attributs InterpolatedStringHandler
et InterpolatedStringHandlerArguments
À compter de C# 10, utilisez ces attributs pour spécifier qu’un type est un gestionnaire de chaîne interpolé. La bibliothèque .NET 6 inclut déjà System.Runtime.CompilerServices.DefaultInterpolatedStringHandler pour les scénarios où vous utilisez une chaîne interpolée comme argument pour un paramètre string
. Vous pouvez avoir d’autres instances pour lesquelles vous souhaitez contrôler la façon dont les chaînes interpolées sont traitées. Vous appliquez System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute au type qui implémente votre gestionnaire. Vous appliquez System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute aux paramètres du constructeur de ce type.
Vous pouvez en savoir plus sur la création d’un gestionnaire de chaînes interpolées dans la spécification de fonctionnalité C# 10 pour les améliorations de chaîne interpolée.
Attribut ModuleInitializer
L’attribut ModuleInitializer
marque une méthode que le runtime appelle lorsque l’assembly se charge. ModuleInitializer
est un alias pour ModuleInitializerAttribute.
L’attribut ModuleInitializer
ne peut être appliqué qu’à une méthode qui :
- Est statique.
- Est sans paramètre.
- Retourne
void
. - Est accessible à partir du module conteneur, c’est à dire,
internal
oupublic
. - N’est pas une méthode générique.
- N’est pas contenue dans une classe générique.
- N’est pas une fonction locale.
L’attribut ModuleInitializer
peut être appliqué à plusieurs méthodes. Dans ce cas, l’ordre dans lequel le runtime les appelle est déterministe, mais pas spécifié.
L’exemple suivant illustre l’utilisation de plusieurs méthodes d’initialiseur de module. Les méthodes Init1
et Init2
s’exécutent avant Main
et chacune ajoute une chaîne à la propriété Text
. Par conséquent, lors de l’exécution de Main
, la propriété Text
a déjà des chaînes provenant des deux méthodes d’initialiseur.
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! ";
}
}
Les générateurs de code source doivent parfois générer du code d’initialisation. Les initialiseurs de module fournissent un emplacement standard pour ce code. Dans la plupart des autres cas, vous devez écrire un constructeur statique au lieu d’un initialiseur de module.
Attribut SkipLocalsInit
L’attribut SkipLocalsInit
empêche le compilateur de définir l’indicateur .locals init
lors de l’émission en métadonnées. L’attribut SkipLocalsInit
est un attribut à usage unique qui peut être appliqué à une méthode, une propriété, une classe, un struct, une interface ou un module, mais pas à un assembly. SkipLocalsInit
est un alias pour SkipLocalsInitAttribute.
L’indicateur .locals init
fait que le CLR initialise toutes les variables locales déclarées dans une méthode sur leurs valeurs par défaut. Étant donné que le compilateur s’assure également que vous n’utilisez jamais une variable avant de lui attribuer une valeur, .locals init
n’est généralement pas nécessaire. Toutefois, l’initialisation zéro supplémentaire peut avoir un impact mesurable sur les performances dans certains scénarios, par exemple, lorsque vous utilisez stackalloc pour allouer un tableau sur la pile. Dans ces cas, vous pouvez ajouter l’attribut SkipLocalsInit
. S’il est appliqué directement à une méthode, l’attribut affecte cette méthode et toutes ses fonctions imbriquées, y compris les fonctions lambda et locales. S’il est appliqué à un type ou à un module, il affecte toutes les méthodes imbriquées à l’intérieur. Cet attribut n’affecte pas les méthodes abstraites, mais il affecte le code généré pour l’implémentation.
Cet attribut nécessite l’option de compilateur AllowUnsafeBlocks. Cette exigence signale que dans certains cas, le code peut afficher la mémoire non affectée (par exemple, la lecture à partir de la mémoire allouée à la pile non initialisée).
L’exemple suivant illustre l’effet de l’attribut SkipLocalsInit
sur une méthode qui utilise stackalloc
. La méthode affiche ce qui était en mémoire lorsque le tableau d’entiers a été alloué.
[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.
Pour essayer ce code vous-même, définissez l’option du compilateur AllowUnsafeBlocks
dans votre fichier .csproj :
<PropertyGroup>
...
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Attribut UnscopedRef
L’attribut UnscopedRef
marque une déclaration de variable comme non délimitée, ce qui signifie que la référence est autorisée à être placée dans une séquence d’échappement.
Vous ajoutez cet attribut où le compilateur traite un ref
comme scoped
implicitement :
- Paramètre
this
pour les méthodes d’instancestruct
. - Paramètres
ref
qui font référence aux typesref struct
. - Paramètres
out
.
L’application de System.Diagnostics.CodeAnalysis.UnscopedRefAttribute marque l’élément comme non délimité.
Attribut OverloadResolutionPriority
Le OverloadResolutionPriorityAttribute permet aux auteurs de bibliothèques de privilégier une surcharge par rapport à une autre lorsque deux surcharges peuvent être ambiguës. Son principal cas d’utilisation est pour les auteurs de bibliothèques qui souhaitent écrire des surcharges offrant de meilleures performances tout en continuant de prendre en charge le code existant sans rupture.
Par exemple, vous pourriez ajouter une nouvelle surcharge qui utilise ReadOnlySpan<T> pour réduire les allocations de mémoire :
[OverloadResolutionPriority(1)]
public void M(params ReadOnlySpan<int> s) => Console.WriteLine("Span");
// Default overload resolution priority of 0
public void M(params int[] a) => Console.WriteLine("Array");
La résolution de surcharge considère les deux méthodes comme équivalentes pour certains types d’arguments. Pour un argument de int[]
, elle privilégie la première surcharge. Pour que le compilateur privilégie la version ReadOnlySpan
, vous pouvez augmenter la priorité de cette surcharge. L’exemple suivant montre l’effet de l’ajout de l’attribut :
var d = new OverloadExample();
int[] arr = [1, 2, 3];
d.M(1, 2, 3, 4); // Prints "Span"
d.M(arr); // Prints "Span" when PriorityAttribute is applied
d.M([1, 2, 3, 4]); // Prints "Span"
d.M(1, 2, 3, 4); // Prints "Span"
Toutes les surcharges avec une priorité inférieure à la priorité de surcharge la plus élevée sont supprimées de l’ensemble des méthodes applicables. Les méthodes sans cet attribut ont une priorité de surcharge définie par défaut à zéro. Les auteurs de bibliothèques devraient utiliser cet attribut en dernier recours lors de l’ajout d’une nouvelle surcharge plus performante. Les auteurs de bibliothèques devraient avoir une compréhension approfondie de la manière dont la résolution de surcharge impacte le choix de la meilleure méthode. Sinon, des erreurs inattendues peuvent se produire.