Dela via


C#-förprocessordirektiv

Även om kompilatorn inte har någon separat förprocessor bearbetar den de direktiv som beskrivs i det här avsnittet som om det fanns en. Använd dessa direktiv för att hjälpa till med villkorlig kompilering. Till skillnad från C- och C++-direktiv kan du inte använda dessa direktiv för att skapa makron. Ett förprocessordirektiv måste vara den enda instruktionen på en rad.

C#-språkreferensen dokumenterar den senaste versionen av C#-språket. Den innehåller även inledande dokumentation för funktioner i offentliga förhandsversioner för den kommande språkversionen.

Dokumentationen identifierar alla funktioner som först introducerades i de tre senaste versionerna av språket eller i aktuella offentliga förhandsversioner.

Tips/Råd

Information om när en funktion först introducerades i C# finns i artikeln om språkversionshistoriken för C#.

Filbaserade appar

Filbaserade appar är program som du kompilerar och kör med hjälp dotnet run Program.cs av (eller valfri *.cs fil). C#-kompilatorn ignorerar dessa preprocessordirektiv, men byggsystemet parsar dem för att producera utdata. Dessa direktiv genererar varningar när de påträffas i en projektbaserad kompilering.

C#-kompilatorn ignorerar alla förprocessordirektiv som börjar med #: eller #!.

Med #! förprocessordirektivet kan Unix-gränssnitt köra en C#-fil direkt med hjälp dotnet runav . Till exempel:

#!/usr/bin/env dotnet run
Console.WriteLine("Hello");

Det föregående kodfragmentet informerar ett Unix-gränssnitt om att köra filen med hjälp dotnet runav . Kommandot /usr/bin/env letar upp den dotnet körbara filen i din PATH, vilket gör den här metoden portabel för olika Unix- och macOS-distributioner. Raden #! måste vara den första raden i filen och följande token är det program som ska köras. Du måste aktivera körningsbehörigheten (x) på C#-filen för den funktionen.

De #: direktiv som används i filbaserade appar beskrivs i referensen för filbaserade appar.

Andra verktyg kan lägga till nya token enligt konventionen #: .

Nullbar kontext

I #nullable förprocessordirektivet anges anteckningar och varningsflaggor i nullbar kontext. Det här direktivet styr om ogiltiga anteckningar har effekt och om nullabilitetsvarningar ges. Varje flagga är antingen inaktiverad eller aktiverad.

Du kan ange båda kontexterna på projektnivå (utanför C#-källkoden) genom att lägga till elementet Nullable i elementet PropertyGroup . #nullable-direktivet styr antecknings- och varningsflaggor och har företräde framför inställningarna på projektnivå. Ett direktiv anger den flagga som det styr tills ett annat direktiv åsidosätter det, eller till slutet av källfilen.

Effekterna av direktiven är följande:

  • #nullable disable: Anger den nullbara kontexten inaktiverad.
  • #nullable enable: Anger det nullbara sammanhanget aktiverat.
  • #nullable restore: Återställer den nullbara kontexten till projektinställningarna.
  • #nullable disable annotations: Sätter annoteringsflaggan i nullbar kontext som inaktiverad.
  • #nullable enable annotations: Anger anteckningsflaggan i den nullbara kontexten till aktiverad.
  • #nullable restore annotations: Återställer annotationsflaggan i nullable-kontexten till projektinställningarna.
  • #nullable disable warnings: Sätter varningsflaggan i nullbar kontext till inaktiverad.
  • #nullable enable warnings: Sätter varningsflaggan i det nullbara sammanhanget till aktiverad.
  • #nullable restore warnings: Återställer varningsflaggan i den nullbara kontexten till projektinställningarna.

Villkorsstyrd kompilering

Använd fyra förprocessordirektiv för att styra villkorlig kompilering:

  • #if: Startar en villkorsstyrd kompilering. Kompilatorn kompilerar endast koden om den angivna symbolen har definierats.
  • #elif: Stänger den föregående villkorliga kompileringen och öppnar en ny villkorlig kompilering baserat på om den angivna symbolen har definierats.
  • #else: Stänger den föregående villkorliga kompileringen och öppnar en ny villkorlig kompilering om den tidigare angivna symbolen inte har definierats.
  • #endif: Stänger den föregående villkorliga kompileringen.

Byggsystemet är också medvetet om fördefinierade förprocessorsymboler som representerar olika målramverk i SDK-liknande projekt. De är användbara när du skapar program som kan rikta in sig på mer än en .NET-version.

Målramverk Symboler Ytterligare symboler
(finns i .NET 5+ SDK:er)
Plattformssymboler (endast tillgängliga
när du anger en OS-specifik TFM)
.NET Framework NETFRAMEWORK, NET481, NET48, NET472, NET471, NET47, NET462, NET461, NET46, , NET452, NET451, NET45, NET40, , NET35NET20 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_GREATERNET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, , NETSTANDARD1_3, NETSTANDARD1_2, , NETSTANDARD1_1NETSTANDARD1_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_GREATERNETSTANDARD1_0_OR_GREATER
.NET 5+ (och .NET Core) NET, NET10_0, NET9_0, NET8_0, NET7_0, NET6_0, NET5_0, NETCOREAPP, NETCOREAPP3_1, , NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, , NETCOREAPP1_1NETCOREAPP1_0 NET10_0_OR_GREATER, NET9_0_OR_GREATER, 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_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, IOS, MACCATALYST, MACOS, , TVOS, , WINDOWS
[OS][version] (till exempel IOS15_1),
[OS][version]_OR_GREATER (till exempel IOS15_1_OR_GREATER)

Kommentar

  • Versionslösa symboler definieras oavsett vilken version du riktar in dig på.
  • Versionsspecifika symboler definieras endast för den version som du riktar in dig på.
  • Symbolerna <framework>_OR_GREATER definieras för den version som du riktar in dig på och alla tidigare versioner. Om du till exempel riktar in dig på .NET Framework 2.0 definieras följande symboler: NET20, NET20_OR_GREATER, NET11_OR_GREATERoch NET10_OR_GREATER.
  • Symbolerna NETSTANDARD<x>_<y>_OR_GREATER definieras endast för .NET Standard-mål och inte för mål som implementerar .NET Standard, till exempel .NET Core och .NET Framework.
  • Dessa skiljer sig från målramverksmonikers (TFM: er) som används av TargetFramework MSBuild och NuGet.

Kommentar

För traditionella, icke-SDK-liknande projekt måste du manuellt konfigurera de villkorliga kompileringssymbolerna för de olika målramverken i Visual Studio via projektets egenskapssidor.

Andra fördefinierade symboler är konstanterna DEBUG och TRACE . Använd #define för att åsidosätta de värden som angetts för projektet. Symbolen DEBUG anges till exempel automatiskt beroende på konfigurationsegenskaperna för bygget ("Felsökning" eller "Release"-läge).

C#-kompilatorn kompilerar koden mellan #if direktivet och #endif direktivet endast om den angivna symbolen definieras eller inte definieras när den ! inte används. Till skillnad från C och C++ kan du inte tilldela ett numeriskt värde till en symbol. #if-instruktionen i C# är boolesk och testar endast om symbolen har definierats eller inte. Följande kod kompileras till exempel när DEBUG har definierats:

#if DEBUG
    Console.WriteLine("Debug version");
#endif

Följande kod kompileras när MYTEST inte har definierats:

#if !MYTEST
    Console.WriteLine("MYTEST is not defined");
#endif

Använd operatorerna == (likhet) och != (ojämlikhet) för att testa värdena booltrue eller false. true betyder att symbolen har definierats. -instruktionen #if DEBUG har samma betydelse som #if (DEBUG == true). Använd operatorerna&& (och), || (eller)och ! (inte) för att utvärdera om flera symboler har definierats. Du kan också gruppera symboler och operatorer med parenteser.

I följande exempel visas ett komplext direktiv som gör att koden kan dra nytta av nyare .NET-funktioner samtidigt som den är bakåtkompatibel. Anta till exempel att du använder ett NuGet-paket i koden, men paketet stöder bara .NET 6 och uppåt, samt .NET Standard 2.0 och uppåt:

#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

#ifMed , tillsammans med direktiven #else, #elif, #endif, #defineoch #undef kan du inkludera eller exkludera kod baserat på förekomsten av en eller flera symboler. Villkorsstyrd kompilering kan vara användbar när du kompilerar kod för en felsökningsversion eller vid kompilering för en specifik konfiguration.

#elif låter dig skapa ett sammansatt villkorligt direktiv. Kompilatorn utvärderar #elif uttrycket om varken föregående #if eller några föregående, valfria #elif direktivuttryck utvärderas till true. Om ett #elif uttryck utvärderas till trueutvärderar kompilatorn all kod mellan #elif och nästa villkorsdirektiv. Till exempel:

#define VC7
//...
#if DEBUG
    Console.WriteLine("Debug build");
#elif VC7
    Console.WriteLine("Visual Studio 7");
#endif

#else låter dig skapa ett sammansatt villkorligt direktiv. Om inget av uttrycken i föregående #if eller (valfria) #elif direktiv utvärderas till trueutvärderar kompilatorn all kod mellan #else och nästa #endif. #endif måste vara nästa förprocessordirektiv efter #else.

#endif anger slutet på ett villkorsdirektiv som började med #if direktivet.

I följande exempel visas hur du definierar en MYTEST symbol i en fil och sedan testar värdena för symbolerna MYTEST och DEBUG . Utdata från det här exemplet beror på om du har skapat projektet i felsöknings - eller versionskonfigurationsläge .

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

I följande exempel visas hur du testar för olika målramverk så att du kan använda nyare API:er när det är möjligt:

public class MyClass
{
    static void Main()
    {
#if NET40
        WebClient _client = new WebClient();
#else
        HttpClient _client = new HttpClient();
#endif
    }
    //...
}

Definiera symboler

Använd följande två förprocessordirektiv för att definiera eller odefiniera symboler för villkorlig kompilering:

  • #define: Definiera en symbol.
  • #undef: Odefiniera en symbol.

Använd #define för att definiera en symbol. När du använder symbolen som uttrycket som skickas till #if-direktivet utvärderas uttrycket till true, som följande exempel visar:

#define VERBOSE

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

Kommentar

I C# ska primitiva konstanter definieras med hjälp av nyckelordet const . En const deklaration skapar en static medlem som inte kan ändras vid körning. Direktivet #define kan inte användas för att deklarera konstanta värden som normalt görs i C och C++. Om du har flera sådana konstanter kan du överväga att skapa en separat "Konstanter"-klass för att lagra dem.

Använd symboler för att ange villkor för kompilering. Testa symbolen med antingen #if eller #elif. Du kan också använda ConditionalAttribute för att utföra villkorlig kompilering. Du kan definiera en symbol, men du kan inte tilldela ett värde till en symbol. Direktivet #define måste visas i filen innan du använder några instruktioner som inte också är förprocessordirektiv. Du kan också definiera en symbol med hjälp av kompilatoralternativet DefineConstants . Odefiniera en symbol med hjälp #undefav .

Definiera regioner

Definiera kodområden som du kan komprimera i en disposition med hjälp av följande två förprocessordirektiv:

  • #region: Starta en region.
  • #endregion: Avsluta en region.

#region låter dig ange ett kodblock som du kan expandera eller komprimera när du använder kodredigerarens dispositionsfunktion. I längre kodfiler är det praktiskt att komprimera eller dölja en eller flera regioner så att du kan fokusera på den del av filen som du arbetar med. I följande exempel visas hur du definierar en region:

#region MyClass definition
public class MyClass
{
    static void Main()
    {
    }
}
#endregion

Ett #region block måste avslutas med ett #endregion direktiv. Ett #region block kan inte överlappa med ett #if block. Ett block kan dock #region kapslas i ett #if block och ett #if block kan kapslas i ett #region block.

Fel- och varningsinformation

Du kan använda följande direktiv för att generera användardefinierade kompilatorfel och varningar samt kontrollradsinformation:

  • #error: Generera ett kompilatorfel med ett angivet meddelande.
  • #warning: Generera en kompilatorvarning med ett specifikt meddelande.
  • #line: Ändra radnumret som skrivs ut med kompilatormeddelanden.

Använd #error för att generera ett användardefinierat CS1029-fel från en specifik plats i koden. Till exempel:

#error Deprecated code in this method.

Kommentar

Kompilatorn behandlar #error version på ett speciellt sätt och rapporterar ett kompilatorfel, CS8304, med ett meddelande som innehåller den använda kompilatorn och språkversionerna.

Använd #warning för att generera en cs1030-nivå en kompilatorvarning från en specifik plats i koden. Till exempel:

#warning Deprecated code in this method.

Använd #line för att ändra kompilatorns radnumrering och (valfritt) filnamnets utdata för fel och varningar.

I följande exempel visas hur du rapporterar två varningar som är associerade med radnummer. Direktivet #line 200 tvingar nästa rads tal att vara 200 (även om standardvärdet är #6), och fram till nästa #line direktiv rapporteras filnamnet som "Special". Direktivet #line default returnerar radnumreringen till standardnumreringen, vilket räknar de rader som omnumrerades av föregående direktiv.

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

Kompilering ger följande utdata:

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

Direktivet #line kan användas i ett automatiserat, mellanliggande steg i byggprocessen. Om du till exempel tar bort rader från den ursprungliga källkodsfilen, men du fortfarande vill att kompilatorn ska generera utdata baserat på den ursprungliga radnumreringen i filen, kan du ta bort rader och sedan simulera den ursprungliga radnumreringen med hjälp #lineav .

Direktivet #line hidden döljer de efterföljande raderna från felsökningsprogrammet, så att när utvecklaren går igenom koden, stegas alla rader mellan ett #line hidden och nästa #line direktiv (förutsatt att det inte är ett annat #line hidden direktiv) över. Det här alternativet kan också användas för att tillåta ASP.NET att skilja mellan användardefinierad och maskingenererad kod. Även om ASP.NET är den primära konsumenten av den här funktionen är det troligt att fler källgeneratorer använder den.

Ett #line hidden direktiv påverkar inte filnamn eller radnummer i felrapportering. Om kompilatorn hittar ett fel i ett dolt block rapporterar kompilatorn det aktuella filnamnet och radnumret för felet.

Direktivet #line filename anger det filnamn som du vill ska visas i kompilatorns utdata. Som standard används det faktiska namnet på källkodsfilen. Filnamnet måste vara inom dubbla citattecken ("") och måste följa ett radnummer.

Du kan använda en ny form av #line-direktivet:

#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;

Komponenterna i det här formuläret är:

  • (1, 1): Startraden och kolumnen för det första tecknet på raden som följer direktivet. I det här exemplet rapporteras nästa rad som rad 1, kolumn 1.
  • (5, 60): Slutraden och kolumnen för den markerade regionen.
  • 10: Kolumnförskjutningen för #line att direktivet ska börja gälla. I det här exemplet rapporteras den tionde kolumnen som kolumn ett. Deklarationen int b = 0; börjar vid den kolumnen. Det här fältet är valfritt. Om det utelämnas börjar direktivet gälla för den första kolumnen.
  • "partial-class.cs": Namnet på utdatafilen.

Föregående exempel genererar följande varning:

partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used

Efter ommappning finns variabeln b på den första raden, med tecknet sex, i filen partial-class.cs.

Domänspecifika språk (DSL:er) använder vanligtvis det här formatet för att ge en bättre mappning från källfilen till de genererade C#-utdata. Den vanligaste användningen av det här utökade #line-direktivet är att mappa om varningar eller fel som visas i en genererad fil till den ursprungliga källan. Tänk dig till exempel den här rakbladssidan:

@page "/"
Time: @DateTime.NowAndThen

Egenskapen DateTime.Now skrivs felaktigt som DateTime.NowAndThen. Det genererade C# för det här rakbladsfragmentet ser ut så här i page.g.cs:

  _builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
  _builder.Add(DateTime.NowAndThen);

Kompilatorns utdata för föregående kodfragment är:

page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'

Rad 2, kolumn 6 i page.razor är den plats där texten @DateTime.NowAndThen börjar, vilket anges av (2, 6) i direktivet. Det intervallet för @DateTime.NowAndThen slutar på rad 2, kolumn 27, som anges av (2, 27) i direktivet. Texten för DateTime.NowAndThen börjar i kolumn 15 i page.g.cs, som anges av 15 i direktivet. Kompilatorn rapporterar felet på platsen i page.razor. Utvecklaren kan navigera direkt till felet i källkoden, inte till den genererade källan.

Mer information om det här formatet finns i funktionsspecifikationen i avsnittet om exempel.

Pragmas

Kompilatorn använder #pragma för att hämta särskilda instruktioner för att kompilera filen där den visas. Kompilatorn måste ha stöd för de pragmas du använder. Med andra ord kan du inte använda #pragma för att skapa anpassade förbearbetningsinstruktioner.

#pragma pragma-name pragma-arguments

pragma-name är namnet på en erkänd pragma. pragma-arguments är de pragmaspecifika argumenten.

#pragma varning

#pragma warning kan aktivera eller inaktivera vissa varningar. #pragma warning disable format och #pragma warning enable format styr hur Visual Studio formaterar kodblock.

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

warning-list är en kommaavgränsad lista med varningsnummer, till exempel 414, CS3021. "CS"-prefixet är valfritt. När du inte anger varningsnummer disable inaktiverar du alla varningar och restore aktiverar alla varningar.

Kommentar

Om du vill hitta varningsnummer i Visual Studio skapar du projektet och letar sedan efter varningsnumren i utdatafönstret.

Börjar disable gälla från och med nästa rad i källfilen. Varningen återställs på raden efter restore. Om det inte finns något restore i filen återställs varningarna till standardtillståndet på den första raden i senare filer i samma kompilering.

// 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()
    {
    }
}

En annan form av warning pragma inaktiverar eller återställer Visual Studio-formateringskommandon i kodblock:

#pragma warning disable format
#pragma warning restore format

Visual Studio-formatkommandon ändrar inte text i kodblock där disable format gäller. Formatkommandon, till exempel Ctrl+ K, Ctrl + D, ändrar inte dessa kodregioner. Den här pragman ger dig fin kontroll över den visuella presentationen av koden.

#pragma kontrollsumma

Genererar kontrollsummor för källfiler som hjälper dig att felsöka ASP.NET sidor.

#pragma checksum "filename" "{guid}" "checksum bytes"

Direktivet använder "filename" som namnet på filen för att övervaka ändringar eller uppdateringar, "{guid}" som globalt unik identifierare (GUID) för hash-algoritmen och "checksum_bytes" som strängen med hexadecimala siffror som representerar byte för kontrollsumman. Du måste ange ett jämnt antal hexadecimala siffror. Ett udda antal siffror resulterar i en kompileringstidsvarning och direktivet ignoreras.

Visual Studio-felsökaren använder en kontrollsumma för att se till att den alltid hittar rätt källa. Kompilatorn beräknar kontrollsumman för en källfil och genererar sedan utdata till programdatabasfilen (PDB). Felsökaren använder PDB för att jämföra med kontrollsumman som den beräknar för källfilen.

Den här lösningen fungerar inte för ASP.NET projekt, eftersom den beräknade kontrollsumman är för den genererade källfilen i stället för den .aspx filen. För att lösa det här problemet #pragma checksum tillhandahåller checksummor stöd för ASP.NET sidor.

När du skapar ett ASP.NET projekt i Visual C# innehåller den genererade källfilen en kontrollsumma för den .aspx fil som källan genereras från. Kompilatorn skriver sedan den här informationen till PDB-filen.

Om kompilatorn inte hittar något #pragma checksum direktiv i filen beräknas kontrollsumman och värdet skrivs till PDB-filen.

class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
    }
}