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
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.
Båda kontexterna kan anges på projektnivå (utanför C#-källkoden) och 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
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.
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
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
. 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).
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.
#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
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.
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
#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.
#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.
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 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 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.
#line 200
-direktivet 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". #line default
-direktivet 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 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ö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. Deklarationenint 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 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, 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
#pragma
ger kompilatorn särskilda instruktioner för kompilering av 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 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 format
och #pragma warning enable format
styr hur Visual Studio formaterar kodblock.
#pragma warning disable warning-list
#pragma warning restore warning-list
Där warning-list
är en kommaavgränsad lista med varningsnummer, till exempel 414, CS3021
. "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()
{
}
}
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 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
}
}