C#-förprocessordirektiv
Även om kompilatorn inte har någon separat förprocessor bearbetas de direktiv som beskrivs i det här avsnittet som om det fanns en. Du använder dem 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.
Nullbar kontext
Föreprocessordirektivet #nullable
anger den nullbara kommentarskontexten och den nullbara varningskontexten. Det här direktivet styr om ogiltiga anteckningar har effekt och om nullabilitetsvarningar ges. Varje kontext är antingen inaktiverad eller aktiverad.
Båda kontexterna kan anges på projektnivå (utanför C#-källkoden) och lägga till elementet Nullable
i elementet PropertyGroup
. Direktivet #nullable
styr antecknings- och varningskontexterna och har företräde framför inställningarna på projektnivå. Ett direktiv anger de kontexter 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 att de nullbara antecknings- och varningskontexterna ska inaktiveras.#nullable enable
: Anger de nullbara antecknings- och varningskontexterna till aktiverade.#nullable restore
: Återställer de nullbara antecknings- och varningskontexterna till projektinställningarna.#nullable disable annotations
: Anger den nullbara anteckningskontexten till inaktiverad.#nullable enable annotations
: Anger den nullbara anteckningskontexten till aktiverad.#nullable restore annotations
: Återställer den nullbara kommentarskontexten till projektinställningarna.#nullable disable warnings
: Anger den nullbara varningskontexten till inaktiverad.#nullable enable warnings
: Anger den nullbara varningskontexten till aktiverad.#nullable restore warnings
: Återställer den nullbara varningskontexten till projektinställningarna.
Villkorsstyrd kompilering
Du använder fyra förprocessordirektiv för att styra villkorlig kompilering:
#if
: Öppnar en villkorlig kompilering, där kod endast kompileras 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.
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 ett numeriskt värde till en symbol inte tilldelas. -instruktionen #if
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
Du kan använda operatorerna ==
(likhet) och !=
(ojämlikhet) för att testa värdena bool
true
eller false
. true
betyder att symbolen har definierats. -instruktionen #if DEBUG
har samma betydelse som #if (DEBUG == true)
. Du kan använda operatorerna &&
(och), ||
(eller)och !
(inte) för att utvärdera om flera symboler har definierats. Du kan också gruppera symboler och operatorer med parenteser.
Följande är 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
#if
Med , tillsammans med direktiven #else
, #elif
, #endif
, #define
och #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.
Ett villkorsdirektiv som börjar med ett #if
direktiv måste uttryckligen avslutas med ett #endif
direktiv. #define
låter dig definiera en symbol. Genom att använda symbolen som uttrycket som skickas till #if
direktivet utvärderas uttrycket till true
. Du kan också definiera en symbol med kompilatoralternativet DefineConstants . Du kan odefiniera en symbol med #undef
. Omfånget för en symbol som skapats med #define
är den fil där den definierades. En symbol som du definierar med DefineConstants eller med står inte i konflikt med #define
en variabel med samma namn. Det betyder att ett variabelnamn inte ska skickas till ett förprocessordirektiv, och en symbol kan bara utvärderas av ett förprocessordirektiv.
#elif
låter dig skapa ett sammansatt villkorligt direktiv. Uttrycket #elif
utvärderas 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 true
utvä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, så att om inget av uttrycken i föregående #if
eller (valfria) #elif
direktiv utvärderas till true
utvärderar kompilatorn all kod mellan #else
och nästa #endif
. #endif
(#endif) måste vara nästa förprocessordirektiv efter #else
.
#endif
anger slutet på ett villkorsdirektiv som började med #if
direktivet.
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 , , 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+ (och .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] (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_GREATER
ochNET10_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 egenskapen MSBuild
TargetFramework
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
. Du kan åsidosätta de värden som angetts för projektet med hjälp av #define
. DEBUG-symbolen anges till exempel automatiskt beroende på byggkonfigurationsegenskaperna ("Felsökning" eller "Release"-läge).
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
Du använder följande två förprocessordirektiv för att definiera eller odefiniera symboler för villkorlig kompilering:
#define
: Definiera en symbol.#undef
: Odefiniera en symbol.
Du använder #define
för att definiera en symbol. När du använder symbolen som det uttryck 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 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.
Symboler kan användas för att ange villkor för kompilering. Du kan 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 kompilatoralternativet DefineConstants . Du kan odefiniera en symbol med #undef
.
Definiera regioner
Du kan definiera kodregioner som kan komprimeras 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 instruerar kompilatorn att generera användardefinierade kompilatorfel och varningar samt kontrollradsinformation med hjälp av följande direktiv:
#error
: Generera ett kompilatorfel med ett angivet meddelande.#warning
: Generera en kompilatorvarning med ett specifikt meddelande.#line
: Ändra radnumret som skrivs ut med kompilatormeddelanden.
#error
låter dig 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.
#warning
låter dig generera en cs1030 nivå en kompilatorvarning från en specifik plats i koden. Till exempel:
#warning Deprecated code in this method.
#line
låter dig ä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, som räknar de rader som numrerades om 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 rader till exempel har tagits bort 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 #line
.
Direktivet #line hidden
döljer efterföljande rader från felsökningsprogrammet, så att när utvecklaren går igenom koden kommer alla rader mellan ett #line hidden
och nästa #line
direktiv (förutsatt att det inte är ett annat #line hidden
direktiv) att kliva ö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öregås av ett radnummer.
Från och med C# 10 kan du 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. Det är där deklarationenint b = 0;
börjar. 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 skulle generera 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 är variabeln , b
på den första raden, på tecken 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
angavs 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. Det framgår av (2, 6)
direktivet. Det intervallet slutar @DateTime.NowAndThen
på rad 2, kolumn 27. Det framgår av (2, 27)
direktivet. Texten för DateTime.NowAndThen
börjar i kolumn 15 i page.g.cs
. Det framgår av 15
direktivet. När du sätter ihop alla argument rapporterar kompilatorn felet på dess plats 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
#pragma
ger kompilatorn särskilda instruktioner för kompilering av filen där den visas. Instruktionerna måste stödjas av kompilatorn. Med andra ord kan du inte använda #pragma
för att skapa anpassade förbearbetningsinstruktioner.
#pragma warning
: Aktivera eller inaktivera varningar.#pragma checksum
: Generera en kontrollsumma.
#pragma pragma-name pragma-arguments
Var pragma-name
är namnet på en erkänd pragma och pragma-arguments
är de pragma-specifika argumenten.
#pragma varning
#pragma warning
kan aktivera eller inaktivera vissa varningar.
#pragma warning disable warning-list
#pragma warning restore warning-list
Var warning-list
finns en kommaavgränsad lista med varningsnummer. "CS"-prefixet är valfritt. När inga varningsnummer har angetts 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ågon 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()
{
}
}
#pragma kontrollsumma
Genererar kontrollsummor för källfiler för felsökning av ASP.NET sidor.
#pragma checksum "filename" "{guid}" "checksum bytes"
Där "filename"
är namnet på filen som kräver övervakning för ändringar eller uppdateringar, "{guid}"
är den globalt unika identifieraren (GUID) för hash-algoritmen och "checksum_bytes"
är strängen med hexadecimala siffror som representerar kontrollsummans byte. Måste vara 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ökningsprogrammet använder sedan PDB för att jämföra med den kontrollsumma 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
}
}