Partager via


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 les indicateurs annotations et warning dans le nullable context. 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 indicateur est soit désactivé soit 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 indicateurs d’annotation et d’avertissement et est prioritaire sur les paramètres au niveau du projet. Une directive définit l’indicateur qu’il contrôle jusqu’à ce qu’une autre directive le remplace, ou jusqu’à la fin du fichier source.

L’effet des directives est le suivant :

  • #nullable disable: définit le contexte nullable sur désactivé.
  • #nullable enable: définit le contexte nullable sur activé.
  • #nullable restore: restaure le contexte nullable aux paramètres du projet.
  • #nullable disable annotations: définit l’indicateur annotations dans le contexte nullable sur désactivé.
  • #nullable enable annotations: définit l’indicateur annotations dans le contexte nullable sur activé.
  • #nullable restore annotations: restaure l’indicateur annotations dans le contexte nullable aux paramètres du projet.
  • #nullable disable warnings: définit l’indicateur warning dans le contexte nullable sur désactivé.
  • #nullable enable warnings: définit l’indicateur warning dans le contexte nullable sur activé.
  • #nullable restore warnings: restaure l’indicateur warning dans le contexte nullable aux 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 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, NET481, 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, NET9_0, 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 et NET10_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 »).

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 est 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 booltrue 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 && (et), les || (ou)et les opérateurs ! (pas) pour déterminer si plusieurs symboles sont définis. Vous pouvez également regrouper des symboles et des opérateurs à l’aide de parenthèses.

L’exemple suivant montre 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.

#elif vous permet de créer une directive conditionnelle composée. L'expression #elif est évaluée si aucune des expressions de directives précédentes #if ni aucune expression de directive facultative précédente #elif 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.

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. Lorsque vous utilisez le symbole comme expression passée à la directive #if, l’expression prend la valeur true, comme l’illustre l’exemple suivant :

#define VERBOSE

#if VERBOSE
   Console.WriteLine("Verbose output version");
#endif

Notes

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 force le numéro de la ligne suivante à être 200 (bien que la valeur par défaut soit #6) et jusqu’à la prochaine directive #line, le nom de fichier est signalé comme « Spécial ». La directive #line default retourne la numérotation de ligne à sa numérotation 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. Bien que ASP.NET soit le principal consommateur de cette fonctionnalité, il est probable que d’autres générateurs sources l’utilisent.

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 trouve une erreur dans un bloc masqué, le compilateur signale le nom de fichier actuel et le numéro de ligne 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 suivre un numéro de ligne.

Vous pouvez utiliser une nouvelle forme de la 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) : La ligne de départ et la colonne du premier caractère de la ligne suivant 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. La déclaration int b = 0; commence à cette colonne. 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 de #line étendue consiste à remapper des avertissements ou des erreurs qui apparaissent dans un fichier généré vers 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 correspond à l’emplacement où commence le texte @DateTime.NowAndThen, noté par (2, 6) dans la directive. Cette étendue de @DateTime.NowAndThen se termine à la ligne 2, colonne 27, notée par la (2, 27) dans la directive. Le texte de DateTime.NowAndThen commence dans la colonne 15 de page.g.cs, noté par le 15 dans la directive. Le compilateur signale l’erreur à l'emplacement 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. Le compilateur doit prendre en charge les pragmas que vous utilisez. En d’autres termes, vous ne pouvez pas utiliser #pragma pour créer des instructions de prétraitement personnalisées.

#pragma pragma-name pragma-arguments

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. Les #pragma warning disable format et #pragma warning enable format contrôlent la façon dont Visual Studio met en forme les blocs de code.

#pragma warning disable warning-list
#pragma warning restore warning-list

warning-list est une liste séparée par des virgules de numéros d’avertissement, comme 414, CS3021. 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()
    {
    }
}

Une autre forme de la warning pragma désactive ou restaure les commandes de mise en forme visual Studio dans des blocs de code :

#pragma warning disable format
#pragma warning restore format

Les commandes de format Visual Studio ne modifient pas le texte dans des blocs de code où disable format est en vigueur. Les commandes de format, telles que Ctrl+K, Ctrl+D, ne modifient pas ces régions de code. Ce pragma vous donne un contrôle précis sur la présentation visuelle de votre code.

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"

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