Directives de préprocesseur C#
Bien que le compilateur n’ait pas de préprocesseur distinct, les directives décrites dans cette section sont traitées comme si c’était le cas. Vous les utilisez pour faciliter la compilation conditionnelle. Contrairement aux directives C et C++, vous ne pouvez pas utiliser ces directives pour créer des macros. Une directive de préprocesseur doit être la seule instruction sur une ligne.
Contexte pouvant accepter la valeur Null
La directive de préprocesseur #nullable
définit le contexte d’annotation pouvant accepter la valeur Null et le contexte d’avertissement pouvant accepter la valeur Null. Cette directive contrôle si les annotations pouvant accepter la valeur Null ont un effet et si des avertissements d’acceptation de la valeur Null sont donnés. Chaque contexte est désactivé ou activé.
Les deux contextes peuvent être spécifiés au niveau du projet (en dehors du code source C#) en ajoutant l’élément Nullable
à l’élément PropertyGroup
. La directive #nullable
contrôle les contextes d’annotation et d’avertissement et elle est prioritaire sur les paramètres au niveau du projet. Une directive définit le ou les contextes qu’elle contrôle jusqu’à ce qu’une autre directive la remplace, ou jusqu’à la fin du fichier source.
L’effet des directives est le suivant :
#nullable disable
: définit les contextes d’avertissement et d’annotation pouvant accepter la valeur Null à désactivés.#nullable enable
: définit les contextes d’avertissement et d’annotation pouvant accepter la valeur Null sur activés.#nullable restore
: restaure les contextes d’avertissement et d’annotation pouvant accepter la valeur Null dans les paramètres du projet.#nullable disable annotations
: définit le contexte d’annotation pouvant accepter la valeur Null sur désactivé.#nullable enable annotations
: définit le contexte d’annotation pouvant accepter la valeur Null sur activé.#nullable restore annotations
: restaure le contexte d’annotation pouvant accepter la valeur Null dans les paramètres du projet.#nullable disable warnings
: définit le contexte d’avertissement nullable sur désactivé.#nullable enable warnings
: définit le contexte d’avertissement nullable sur activé.#nullable restore warnings
: restaure le contexte d’avertissement nullable avec les paramètres du projet.
Compilation conditionnelle
Vous utilisez quatre directives de préprocesseur pour contrôler la compilation conditionnelle :
#if
: ouvre une compilation conditionnelle, où le code est compilé uniquement si le symbole spécifié est défini.#elif
: ferme la compilation conditionnelle précédente et ouvre une nouvelle compilation conditionnelle en fonction de la définition du symbole spécifié.#else
: ferme la compilation conditionnelle précédente et ouvre une nouvelle compilation conditionnelle si le symbole spécifié précédent n’est pas défini.#endif
: ferme la compilation conditionnelle précédente.
Le compilateur C# compile le code entre la directive #if
et la directive #endif
uniquement si le symbole spécifié est défini, ou non défini lorsque l’opérateur Not !
est utilisé. Contrairement à C et C++, vous ne pouvez pas attribuer de valeur numérique à un symbole. L’instruction #if
en C# est booléenne et teste uniquement si le symbole a été défini ou non. Par exemple, le code suivant est compilé quand DEBUG
est défini :
#if DEBUG
Console.WriteLine("Debug version");
#endif
Le code suivant est compilé quand MYTEST
n’est pas défini :
#if !MYTEST
Console.WriteLine("MYTEST is not defined");
#endif
Vous pouvez utiliser les opérateurs ==
(égalité) et !=
(inégalité) pour tester les valeurs bool
true
ou false
. true
signifie que le symbole est défini. L’instruction #if DEBUG
a la même signification que #if (DEBUG == true)
. Vous pouvez utiliser les opérateurs &&
(et), ||
(ou), et !
(non) pour déterminer si plusieurs symboles ont été définis. Vous pouvez également regrouper des symboles et des opérateurs à l’aide de parenthèses.
Voici une directive complexe qui permet à votre code de tirer parti des fonctionnalités .NET plus récentes tout en restant rétrocompatible. Par exemple, imaginez que vous utilisez un package NuGet dans votre code, mais que le package prend uniquement en charge .NET 6 et versions ultérieures, ainsi que .NET Standard 2.0 et versions ultérieures :
#if (NET6_0_OR_GREATER || NETSTANDARD2_0_OR_GREATER)
Console.WriteLine("Using .NET 6+ or .NET Standard 2+ code.");
#else
Console.WriteLine("Using older code that doesn't support the above .NET versions.");
#endif
#if
, ainsi que les directives #else
, #elif
, #endif
, #define
et #undef
, vous permettent d’inclure ou d’exclure du code en fonction de l’existence d’un ou plusieurs symboles. La compilation conditionnelle peut être utile lors de la compilation du code pour une version Debug ou lors de la compilation d’une configuration spécifique.
Une directive conditionnelle commençant par une directive #if
doit se terminer explicitement par une directive #endif
. #define
vous permet de définir un symbole. En utilisant ce dernier comme expression passée à la directive #if
, l’expression correspond à true
. Vous pouvez également définir un symbole avec l’option de compilateur DefineConstants. Vous pouvez annuler la définition d’un symbole avec #undef
. La portée d’un symbole créé avec #define
est le fichier dans lequel il a été défini. Un symbole que vous définissez avec DefineConstants ou #define
n’est pas en conflit avec une variable du même nom. Autrement dit, un nom de variable ne doit pas être passé à une directive de préprocesseur, et un symbole ne peut être évalué que par une directive de préprocesseur.
#elif
vous permet de créer une directive conditionnelle composée. L’expression #elif
est évaluée si ni l’expression #if
précédente ni une expression de directive #elif
facultative précédente n’est évaluée à true
. Si une expression #elif
est évaluée à true
, le compilateur évalue l’ensemble du code situé entre #elif
et la directive conditionnelle suivante. Par exemple :
#define VC7
//...
#if DEBUG
Console.WriteLine("Debug build");
#elif VC7
Console.WriteLine("Visual Studio 7");
#endif
#else
vous permet de créer une directive conditionnelle composée de sorte que, si aucune des expressions contenues dans les précédentes directives #if
ou #elif
(facultative) n’a la valeur true
, le compilateur évalue tout le code entre #else
et la directive #endif
suivante. #endif
(#endif) doit être la directive de préprocesseur suivante après #else
.
#endif
spécifie la fin d’une directive conditionnelle, qui a commencé avec la directive #if
.
Le système de génération tient également compte des symboles de préprocesseur prédéfinis représentant différents frameworks cibles dans les projets de type SDK. Ils sont utiles durant la création d’applications pouvant cibler plusieurs versions de .NET.
Versions cibles de .NET Framework | symboles | Symboles supplémentaires (disponibles dans les SDK .NET 5+) |
Symboles de plateforme (disponibles uniquement lorsque vous spécifiez un moniker de framework cible spécifique au système d’exploitation) |
---|---|---|---|
.NET Framework | NETFRAMEWORK , NET48 , NET472 , NET471 , NET47 , NET462 , NET461 , NET46 , NET452 , NET451 , NET45 , NET40 , NET35 , NET20 |
NET48_OR_GREATER , NET472_OR_GREATER , NET471_OR_GREATER , NET47_OR_GREATER , NET462_OR_GREATER , NET461_OR_GREATER , NET46_OR_GREATER , NET452_OR_GREATER , NET451_OR_GREATER , NET45_OR_GREATER , NET40_OR_GREATER , NET35_OR_GREATER , NET20_OR_GREATER |
|
.NET Standard | NETSTANDARD , NETSTANDARD2_1 , NETSTANDARD2_0 , NETSTANDARD1_6 , NETSTANDARD1_5 , NETSTANDARD1_4 , NETSTANDARD1_3 , NETSTANDARD1_2 , NETSTANDARD1_1 , NETSTANDARD1_0 |
NETSTANDARD2_1_OR_GREATER , NETSTANDARD2_0_OR_GREATER , NETSTANDARD1_6_OR_GREATER , NETSTANDARD1_5_OR_GREATER , NETSTANDARD1_4_OR_GREATER , NETSTANDARD1_3_OR_GREATER , NETSTANDARD1_2_OR_GREATER , NETSTANDARD1_1_OR_GREATER , NETSTANDARD1_0_OR_GREATER |
|
.NET 5+ (et .NET Core) | NET , NET8_0 , NET7_0 , NET6_0 , NET5_0 , NETCOREAPP , NETCOREAPP3_1 , NETCOREAPP3_0 , NETCOREAPP2_2 , NETCOREAPP2_1 , NETCOREAPP2_0 , NETCOREAPP1_1 , NETCOREAPP1_0 |
NET8_0_OR_GREATER , NET7_0_OR_GREATER , NET6_0_OR_GREATER , NET5_0_OR_GREATER , NETCOREAPP3_1_OR_GREATER , NETCOREAPP3_0_OR_GREATER , NETCOREAPP2_2_OR_GREATER , NETCOREAPP2_1_OR_GREATER , NETCOREAPP2_0_OR_GREATER , NETCOREAPP1_1_OR_GREATER , NETCOREAPP1_0_OR_GREATER |
ANDROID , BROWSER , IOS , MACCATALYST , MACOS , TVOS , WINDOWS ,[OS][version] (par exemple, IOS15_1 ),[OS][version]_OR_GREATER (par exemple IOS15_1_OR_GREATER ) |
Notes
- Les symboles sans version sont définis indépendamment de la version que vous ciblez.
- Les symboles spécifiques à la version sont définis uniquement pour la version que vous ciblez.
- Les symboles
<framework>_OR_GREATER
sont définis pour la version que vous ciblez et toutes les versions antérieures. Par exemple, si vous ciblez .NET Framework 2.0, les symboles suivants sont définis :NET20
,NET20_OR_GREATER
,NET11_OR_GREATER
etNET10_OR_GREATER
. - Les symboles
NETSTANDARD<x>_<y>_OR_GREATER
sont définis uniquement pour les cibles .NET Standard, et non pour les cibles qui implémentent .NET Standard, telles que .NET Core et .NET Framework. - Ils sont différents des monikers de framework cible utilisés par la propriété MSBuild
TargetFramework
et NuGet.
Notes
Pour les projets non-SDK traditionnels, vous devez configurer manuellement les symboles de compilation conditionnelle pour les différents frameworks cibles dans Visual Studio via les pages de propriétés du projet.
D’autres symboles prédéfinis incluent les constantes DEBUG
et TRACE
. Vous pouvez remplacer les valeurs définies pour le projet à l’aide de #define
. Le symbole DEBUG, par exemple, est automatiquement défini en fonction de vos propriétés de configuration de build (mode « Debug » ou « Release »).
L’exemple suivant montre comment définir un symbole MYTEST
sur un fichier, puis tester les valeurs des symboles MYTEST
et DEBUG
. La sortie de cet exemple dépend du mode de configuration que vous avez utilisé pour créer le projet (Debug ou Release).
#define MYTEST
using System;
public class MyClass
{
static void Main()
{
#if (DEBUG && !MYTEST)
Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && MYTEST)
Console.WriteLine("MYTEST is defined");
#elif (DEBUG && MYTEST)
Console.WriteLine("DEBUG and MYTEST are defined");
#else
Console.WriteLine("DEBUG and MYTEST are not defined");
#endif
}
}
L’exemple suivant montre comment tester différents frameworks cibles pour pouvoir utiliser les nouvelles API dans la mesure du possible :
public class MyClass
{
static void Main()
{
#if NET40
WebClient _client = new WebClient();
#else
HttpClient _client = new HttpClient();
#endif
}
//...
}
Définition de symboles
Vous utilisez les deux directives de préprocesseur suivantes pour définir ou annuler la définition de symboles pour la compilation conditionnelle :
#define
: permet de définir un symbole.#undef
: permet d’annuler la définition d’un symbole.
Vous utilisez #define
pour définir un symbole. Quand vous utilisez le symbole sous forme d’expression passée à la directive #if
, l’expression donne la valeur true
, comme illustré dans l’exemple suivant :
#define VERBOSE
#if VERBOSE
Console.WriteLine("Verbose output version");
#endif
Remarque
En C#, les constantes primitives doivent être définies à l’aide du mot clé const
. Une déclaration const
crée un membre static
qui ne peut pas être modifié au moment de l’exécution. La directive #define
ne peut pas être utilisée pour déclarer des valeurs constantes comme c’est le cas habituellement en C et en C++. Si vous avez plusieurs constantes de ce type, créez une classe « Constantes » distincte pour les stocker.
Les symboles peuvent être utilisés pour spécifier des conditions de compilation. Vous pouvez tester le symbole avec #if
ou #elif
. Vous pouvez également utiliser ConditionalAttribute pour effectuer une compilation conditionnelle. Vous pouvez définir un symbole, mais vous ne pouvez pas attribuer de valeur à un symbole. La directive #define
doit apparaître dans le fichier avant l’utilisation d’instructions qui ne sont pas également des directives de préprocesseur. Vous pouvez également définir un symbole avec l’option de compilateur DefineConstants. Vous pouvez annuler la définition d’un symbole avec #undef
.
Définition de régions
Vous pouvez définir des régions de code qui peuvent être réduites dans un plan à l’aide des deux directives de préprocesseur suivantes :
#region
: permet de démarrer une région.#endregion
: permet de mettre fin à une région.
#region
vous permet de spécifier un bloc de code que vous pouvez développer ou réduire quand vous utilisez la fonctionnalité Mode Plan de l’éditeur de code. Dans les fichiers de code volumineux, il peut être pratique de réduire ou masquer une ou plusieurs régions pour vous concentrer sur la partie du fichier sur laquelle vous êtes en train de travailler. L’exemple suivant montre comment définir une région :
#region MyClass definition
public class MyClass
{
static void Main()
{
}
}
#endregion
Un bloc #region
doit se terminer par une directive #endregion
. Un bloc #region
ne peut pas chevaucher un bloc #if
. Toutefois, un bloc #region
peut être imbriqué dans un bloc #if
et, inversement, un bloc #if
peut être imbriqué dans un bloc #region
.
Informations d’erreur et d’avertissement
Vous demandez au compilateur de générer des erreurs et des avertissements du compilateur définis par l’utilisateur, ainsi que des informations de ligne de contrôle à l’aide des directives suivantes :
#error
: permet de générer une erreur du compilateur avec un message spécifié.#warning
: permet de générer un avertissement du compilateur, avec un message spécifique.#line
: permet de modifier le numéro de ligne imprimé avec les messages du compilateur.
#error
vous permet de générer une erreur définie par l’utilisateur CS1029 à partir d’un emplacement spécifique dans votre code. Par exemple :
#error Deprecated code in this method.
Notes
Le compilateur traite #error version
de manière spéciale et signale une erreur de compilateur CS8304, avec un message contenant les versions du compilateur et du langage utilisés.
#warning
vous permet de générer un avertissement du compilateur de premier niveau CS1030 à partir d’un emplacement spécifique dans votre code. Par exemple :
#warning Deprecated code in this method.
#line
vous permet de changer la numérotation de lignes du compilateur et (éventuellement) le nom de fichier pour les erreurs et les avertissements.
L’exemple suivant montre comment signaler deux avertissements associés à des numéros de ligne. La directive #line 200
assigne de force le numéro 200 à la ligne suivante (bien que le numéro par défaut soit 6) ; jusqu’à la prochaine directive #line
, le nom de fichier indiqué est « Special ». La directive #line default
rétablit la numérotation de lignes par défaut, qui compte les lignes renumérotées par la directive précédente.
class MainClass
{
static void Main()
{
#line 200 "Special"
int i;
int j;
#line default
char c;
float f;
#line hidden // numbering not affected
string s;
double d;
}
}
La compilation produit la sortie suivante :
Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used
La directive #line
peut être utilisée dans une étape intermédiaire automatisée du processus de génération. Par exemple, si des lignes ont été supprimées du fichier de code source d’origine, mais que vous voulez que le compilateur continue de générer une sortie sur la base de la numérotation de lignes d’origine du fichier, vous pouvez supprimer les lignes et simuler ensuite la numérotation de lignes d’origine avec #line
.
La directive #line hidden
masque les lignes successives du débogueur, de sorte que quand le développeur parcourt le code, les lignes situées entre une directive #line hidden
et la directive #line
suivante (en supposant qu’il ne s’agit pas d’une autre directive #line hidden
) sont ignorées. Cette option peut aussi être utilisée pour permettre à ASP.NET de différencier le code défini par l’utilisateur du code généré par l’ordinateur. Même si ASP.NET est le principal utilisateur de cette fonctionnalité, il est probable que d’autres générateurs de code source pourront l’utiliser.
Une directive #line hidden
n’affecte ni les noms de fichiers ni les numéros de lignes dans les rapports d’erreurs. Autrement dit, si le compilateur repère une erreur dans un bloc masqué, il indique le nom du fichier et le numéro de ligne actuels de l’erreur.
La directive #line filename
spécifie le nom de fichier que vous souhaitez voir apparaître dans la sortie du compilateur. Par défaut, le nom réel du fichier de code source est utilisé. Le nom de fichier doit être entre guillemets doubles (" ") et doit être précédé d’un numéro de ligne.
À compter de C# 10, vous pouvez utiliser une nouvelle forme de directive #line
:
#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;
Les composants de cette forme sont les suivants :
(1, 1)
: ligne de début et colonne pour le premier caractère de la ligne qui suit la directive. Dans cet exemple, la ligne suivante est signalée comme ligne 1, colonne 1.(5, 60)
: ligne de fin et colonne pour la région marquée.10
: décalage de colonne pour que la directive#line
prenne effet. Dans cet exemple, la 10e colonne serait signalée comme colonne 1. C’est là que commence la déclarationint b = 0;
. Ce champ est facultatif. En cas d’omission, la directive prend effet sur la première colonne."partial-class.cs"
: nom du fichier de sortie.
L’exemple précédent génère l’avertissement suivant :
partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used
Après le remappage, la variable b
se trouve sur la première ligne, au caractère six, du fichier partial-class.cs
.
Les langages spécifiques à un domaine utilisent généralement ce format pour fournir un meilleur mappage du fichier source à la sortie C# générée. L’utilisation la plus courante de cette directive #line
étendue consiste à mapper des avertissements ou des erreurs qui apparaissent dans un fichier généré à la source d’origine. Prenons comme exemple cette page Razor :
@page "/"
Time: @DateTime.NowAndThen
La propriété DateTime.Now
a été tapée de manière incorrecte en tant que DateTime.NowAndThen
. Le code C# généré pour cet extrait de code Razor ressemble à ce qui suit, dans page.g.cs
:
_builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
_builder.Add(DateTime.NowAndThen);
La sortie du compilateur pour l’extrait de code précédent est la suivante :
page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'
La ligne 2, colonne 6 dans page.razor
est l’endroit où commence le texte @DateTime.NowAndThen
. Voici ce qui est indiqué dans la directive : (2, 6)
. Cette étendue de @DateTime.NowAndThen
se termine à la ligne 2, colonne 27. Voici ce qui est indiqué dans la directive : (2, 27)
. Le texte pour DateTime.NowAndThen
commence dans la colonne 15 de page.g.cs
. Voici ce qui est indiqué dans la directive : 15
. En plaçant tous les arguments ensemble, le compilateur signale l’erreur à son emplacement dans page.razor
. Le développeur peut accéder directement à l’erreur dans son code source, et non dans la source générée.
Pour afficher d’autres exemples de ce format, consultez la spécification de fonctionnalité dans la section sur les exemples.
Pragmas
La directive #pragma
fournit au compilateur des instructions spéciales pour la compilation du fichier dans lequel elle apparaît. Les instructions doivent être prises en charge par le compilateur. En d’autres termes, vous ne pouvez pas utiliser #pragma
pour créer des instructions de prétraitement personnalisées.
#pragma warning
: permet d’activer ou de désactiver les avertissements.#pragma checksum
: permet de générer une somme de contrôle.
#pragma pragma-name pragma-arguments
Où pragma-name
est le nom d’un pragma reconnu et pragma-arguments
correspond aux arguments spécifiques au pragma.
#pragma warning
#pragma warning
peut activer ou désactiver certains avertissements.
#pragma warning disable warning-list
#pragma warning restore warning-list
Où warning-list
est une liste séparée par des virgules de numéros d’avertissement. Le préfixe « CS » est facultatif. Quand aucun numéro d’avertissement n’est spécifié, disable
désactive tous les avertissements et restore
active tous les avertissements.
Notes
Pour trouver les numéros d’avertissement dans Visual Studio, générez votre projet, puis recherchez les numéros d’avertissement dans la fenêtre Sortie.
disable
prend effet à partir de la ligne suivante du fichier source. L’avertissement est restauré sur la ligne suivant l’élément restore
. En l’absence d’élément restore
dans le fichier, les avertissements sont restaurés à leur état par défaut à la première ligne de tous les fichiers ultérieurs de la même compilation.
// pragma_warning.cs
using System;
#pragma warning disable 414, CS3021
[CLSCompliant(false)]
public class C
{
int i = 1;
static void Main()
{
}
}
#pragma warning restore CS3021
[CLSCompliant(false)] // CS3021
public class D
{
int i = 1;
public static void F()
{
}
}
checksum #pragma
Génère des sommes de contrôle pour les fichiers sources afin de faciliter le débogage des pages ASP.NET.
#pragma checksum "filename" "{guid}" "checksum bytes"
Où "filename"
est le nom du fichier qui nécessite la surveillance des modifications ou des mises à jour, "{guid}"
est l’identificateur global unique (GUID) de l’algorithme de hachage, et "checksum_bytes"
est la chaîne de chiffres hexadécimaux représentant les octets de la somme de contrôle. Doit être un nombre pair de chiffres hexadécimaux. S’il y a un nombre impair de chiffres, un avertissement est généré au moment de la compilation et la directive est ignorée.
Le débogueur Visual Studio utilise une somme de contrôle pour s’assurer de toujours trouver la bonne source. Le compilateur calcule la somme de contrôle pour un fichier source, puis envoie la sortie vers le fichier de base de données du programme (PDB). Le débogueur utilise ensuite le fichier PDB à comparer avec la somme de contrôle qu’il calcule pour le fichier source.
Cette solution ne fonctionne pas pour les projets ASP.NET, car la somme de contrôle est calculée pour le fichier source généré, au lieu du fichier .aspx. Pour résoudre ce problème, #pragma checksum
fournit une prise en charge de la somme de contrôle pour les pages ASP.NET.
Quand vous créez un projet ASP.NET dans Visual C#, le fichier source généré contient une somme de contrôle pour le fichier .aspx, à partir duquel la source est générée. Le compilateur écrit ensuite ces informations dans le fichier PDB.
Si le compilateur ne rencontre aucune directive #pragma checksum
dans le fichier, il calcule la somme de contrôle et écrit la valeur dans le fichier PDB.
class TestClass
{
static int Main()
{
#pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
}
}