Direktivy preprocesoru jazyka C#
I když kompilátor nemá samostatný preprocesor, direktivy popsané v této části se zpracovávají, jako by existovaly. Slouží k usnadnění podmíněné kompilace. Na rozdíl od direktiv C a C++ nemůžete tyto direktivy použít k vytváření maker. Direktiva preprocesoru musí být jedinou instrukcí na řádku.
Kontext s možnou hodnotou null
Direktiva #nullable
preprocesoru nastaví kontext poznámek s možnou hodnotou null a kontext upozornění s možnou hodnotou null. Tato direktiva určuje, jestli mají poznámky s možnou hodnotou null účinek a zda jsou uvedena upozornění s možnou hodnotou null. Každý kontext je zakázaný nebo povolený.
Oba kontexty lze zadat na úrovni projektu (mimo zdrojový kód jazyka C#) přidáním Nullable
elementu do elementu PropertyGroup
. Direktiva #nullable
řídí kontexty poznámek a upozornění a má přednost před nastavením na úrovni projektu. Direktiva nastaví kontexty, které řídí, dokud ji nepřepíše jiná direktiva, nebo až do konce zdrojového souboru.
Účinek direktiv je následující:
#nullable disable
: Nastaví kontexty poznámek s možnou hodnotou null a upozornění na zakázáno.#nullable enable
: Nastaví kontexty s možnou hodnotou null a upozornění na povolenou.#nullable restore
: Obnoví kontexty poznámek s možnou hodnotou null a upozornění do nastavení projektu.#nullable disable annotations
: Nastaví kontext poznámek s možnou hodnotou null na zakázáno.#nullable enable annotations
: Nastaví kontext poznámek s možnou hodnotou null na povolenou.#nullable restore annotations
: Obnoví kontext poznámek s možnou hodnotou null do nastavení projektu.#nullable disable warnings
: Nastaví kontext upozornění s možnou hodnotou null na zakázáno.#nullable enable warnings
: Nastaví kontext upozornění s možnou hodnotou null na povolenou.#nullable restore warnings
: Obnoví kontext upozornění s možnou hodnotou null do nastavení projektu.
Podmíněná kompilace
K řízení podmíněné kompilace se používají čtyři direktivy preprocesoru:
#if
: Otevře podmíněnou kompilaci, kde je kód zkompilován pouze v případě, že je definovaný zadaný symbol.#elif
: Zavře předchozí podmíněnou kompilaci a otevře novou podmíněnou kompilaci na základě toho, jestli je definovaný zadaný symbol.#else
: Zavře předchozí podmíněnou kompilaci a otevře novou podmíněnou kompilaci, pokud není definovaný předchozí zadaný symbol.#endif
: Zavře předchozí podmíněnou kompilaci.
Kompilátor jazyka C# zkompiluje kód mezi direktivou #if
a #endif
direktivou pouze v případě, že je definovaný zadaný symbol nebo není definován při použití operátoru !
. Na rozdíl od jazyka C a C++ nelze přiřadit číselnou hodnotu symbolu. Příkaz #if
v jazyce C# je logická hodnota a testuje pouze to, jestli byl symbol definován, nebo ne. Například následující kód je zkompilován při DEBUG
definování:
#if DEBUG
Console.WriteLine("Debug version");
#endif
Následující kód je zkompilován, pokud MYTEST
není definován:
#if !MYTEST
Console.WriteLine("MYTEST is not defined");
#endif
Operátory ==
(rovnost) a!=
(nerovnost) můžete použít k otestování bool
hodnot true
nebo false
. true
znamená, že je definován symbol. #if DEBUG
Příkaz má stejný význam jako #if (DEBUG == true)
. Operátory &&
(a) (nebo) a !
(ne) můžete použít k vyhodnocení, ||
jestli bylo definováno více symbolů. Symboly a operátory je také možné seskupovat pomocí závorek.
Následuje složitá direktiva, která umožňuje kódu využívat novější funkce .NET a zůstat zpětně kompatibilní. Představte si například, že ve svém kódu používáte balíček NuGet, ale balíček podporuje pouze .NET 6 a novější, stejně jako .NET Standard 2.0 a novější:
#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
spolu s direktivami #else
, , #elif
, #endif
#define
a #undef
direktivami umožňuje zahrnout nebo vyloučit kód na základě existence jednoho nebo více symbolů. Podmíněná kompilace může být užitečná při kompilaci kódu pro sestavení ladění nebo při kompilaci pro konkrétní konfiguraci.
Podmíněná direktiva začínající direktivou #if
musí být explicitně ukončena direktivou #endif
. #define
umožňuje definovat symbol. Použitím symbolu jako výrazu předaného #if
direktivě se výraz vyhodnotí jako true
. Symbol můžete také definovat pomocí možnosti Kompilátor DefineConstants. Symbol můžete nedefinovat pomocí #undef
znaku . Obor symbolu vytvořeného pomocí #define
je soubor, ve kterém byl definován. Symbol, který definujete pomocí DefineConstants nebo s #define
, není v konfliktu s proměnnou se stejným názvem. To znamená, že název proměnné by neměl být předán direktivě preprocesoru a symbol lze vyhodnotit pouze direktivou preprocesoru.
Výraz #elif
umožňuje vytvořit složenou podmíněnou direktivu. Výraz #elif
se vyhodnotí, pokud se nevyhodnotí předchozí #if
ani předchozí, volitelné výrazy #elif
direktivy .true
Pokud se #elif
výraz vyhodnotí jako true
, kompilátor vyhodnotí veškerý kód mezi #elif
a další podmíněnou direktivou. Příklad:
#define VC7
//...
#if DEBUG
Console.WriteLine("Debug build");
#elif VC7
Console.WriteLine("Visual Studio 7");
#endif
#else
umožňuje vytvořit složenou podmíněnou direktivu, takže pokud se žádný z výrazů v předchozích #if
nebo (volitelných) #elif
direktivách vyhodnotí true
jako , kompilátor vyhodnotí veškerý kód mezi #else
a dalším #endif
. #endif
(#endif) musí být další direktiva preprocesoru za #else
.
#endif
určuje konec podmíněné direktivy, která začala direktivou #if
.
Systém sestavení je také informován o předdefinovaných symbolech preprocesoru představujících různé cílové architektury v projektech ve stylu sady SDK. Jsou užitečné při vytváření aplikací, které můžou cílit na více než jednu verzi .NET.
Cílové architektury | Symboly | Další symboly (k dispozici v sadách .NET 5+ SDK) |
Symboly platformy (k dispozici pouze při zadání TFM specifického pro operační systém) |
---|---|---|---|
.NET Framework | NETFRAMEWORK , NET481 , , , NET471 NET47 NET45 NET40 NET472 NET461 NET35 NET462 NET46 NET452 NET451 NET48 NET20 |
NET48_OR_GREATER , NET472_OR_GREATER , , NET471_OR_GREATER , NET462_OR_GREATER NET47_OR_GREATER , NET461_OR_GREATER NET40_OR_GREATER NET452_OR_GREATER NET46_OR_GREATER NET451_OR_GREATER NET45_OR_GREATER , NET35_OR_GREATER NET20_OR_GREATER |
|
.NET Standard | NETSTANDARD , NETSTANDARD2_1 , , NETSTANDARD2_0 , NETSTANDARD1_5 NETSTANDARD1_6 , NETSTANDARD1_4 NETSTANDARD1_3 NETSTANDARD1_2 , , NETSTANDARD1_1 NETSTANDARD1_0 |
NETSTANDARD2_1_OR_GREATER , NETSTANDARD2_0_OR_GREATER , , NETSTANDARD1_5_OR_GREATER NETSTANDARD1_3_OR_GREATER NETSTANDARD1_2_OR_GREATER NETSTANDARD1_6_OR_GREATER NETSTANDARD1_4_OR_GREATER , , NETSTANDARD1_1_OR_GREATER NETSTANDARD1_0_OR_GREATER |
|
.NET 5+ (a .NET Core) | NET , NET9_0 , , NET8_0 , NET6_0 NET5_0 , NETCOREAPP2_0 NETCOREAPP1_1 NET7_0 NETCOREAPP NETCOREAPP3_1 NETCOREAPP3_0 NETCOREAPP2_2 NETCOREAPP2_1 NETCOREAPP1_0 |
NET8_0_OR_GREATER , NET7_0_OR_GREATER , , NET6_0_OR_GREATER , NETCOREAPP3_1_OR_GREATER NETCOREAPP2_0_OR_GREATER NET5_0_OR_GREATER NETCOREAPP3_0_OR_GREATER NETCOREAPP2_2_OR_GREATER NETCOREAPP2_1_OR_GREATER , NETCOREAPP1_1_OR_GREATER NETCOREAPP1_0_OR_GREATER |
ANDROID , BROWSER , IOS , , MACOS MACCATALYST , TVOS , , WINDOWS [OS][version] (například IOS15_1 ),[OS][version]_OR_GREATER (například IOS15_1_OR_GREATER ) |
Poznámka:
- Symboly bez verzí se definují bez ohledu na verzi, na kterou cílíte.
- Symboly specifické pro verzi jsou definované jenom pro verzi, na kterou cílíte.
- Symboly
<framework>_OR_GREATER
jsou definované pro verzi, na kterou cílíte, a všechny předchozí verze. Pokud například cílíte na rozhraní .NET Framework 2.0, jsou definovány následující symboly:NET20
,NET20_OR_GREATER
,NET11_OR_GREATER
aNET10_OR_GREATER
. - Symboly
NETSTANDARD<x>_<y>_OR_GREATER
jsou definovány pouze pro cíle .NET Standard, a ne pro cíle, které implementují .NET Standard, jako jsou .NET Core a .NET Framework. - Liší se od monikers cílové architektury (TFMs) používané vlastností MSBuild
TargetFramework
a NuGet.
Poznámka:
U tradičních projektů, které nejsou ve stylu sady SDK, je nutné ručně nakonfigurovat symboly podmíněné kompilace pro různé cílové architektury v sadě Visual Studio prostřednictvím stránek vlastností projektu.
Mezi další předdefinované symboly patří DEBUG
konstanty.TRACE
Hodnoty nastavené pro projekt můžete přepsat pomocí #define
. Například symbol DEBUG se nastavuje automaticky v závislosti na vlastnostech konfigurace sestavení (režim Ladění nebo Release).
Následující příklad ukazuje, jak definovat MYTEST
symbol v souboru a pak otestovat hodnoty MYTEST
a DEBUG
symboly. Výstup tohoto příkladu závisí na tom, jestli jste projekt vytvořili v režimu konfigurace ladění nebo vydané verze .
#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
}
}
Následující příklad ukazuje, jak otestovat různé cílové architektury, abyste mohli používat novější rozhraní API, pokud je to možné:
public class MyClass
{
static void Main()
{
#if NET40
WebClient _client = new WebClient();
#else
HttpClient _client = new HttpClient();
#endif
}
//...
}
Definování symbolů
Pomocí následujících dvou direktiv preprocesoru definujete nebo nedefinujete symboly pro podmíněnou kompilaci:
#define
: Definujte symbol.#undef
: Nedefinovat symbol.
Slouží #define
k definování symbolu. Pokud použijete symbol jako výraz předaný direktivě #if
, výraz se vyhodnotí jako true
, jak ukazuje následující příklad:
#define VERBOSE
#if VERBOSE
Console.WriteLine("Verbose output version");
#endif
Poznámka:
V jazyce C# by měly být primitivní konstanty definovány pomocí klíčového const
slova. Deklarace const
vytvoří static
člena, který nelze upravovat za běhu. Direktivu #define
nelze použít k deklaraci konstantních hodnot, jak se obvykle provádí v jazyce C a C++. Pokud máte několik takových konstant, zvažte vytvoření samostatné třídy "Konstanty", která je bude uchovávat.
Symboly lze použít k určení podmínek kompilace. Symbol můžete otestovat pomocí symbolu #if
nebo #elif
. Můžete také použít ConditionalAttribute k provedení podmíněné kompilace. Symbol můžete definovat, ale nemůžete přiřadit hodnotu symbolu. Před #define
použitím jakýchkoli pokynů, které nejsou také direktivy preprocesoru, musí být direktiva v souboru uvedena. Symbol můžete také definovat pomocí možnosti Kompilátor DefineConstants. Symbol můžete nedefinovat pomocí #undef
znaku .
Definování oblastí
Oblasti kódu, které lze sbalit v osnově, můžete definovat pomocí následujících dvou direktiv preprocesoru:
#region
: Spusťte oblast.#endregion
: Ukončení oblasti.
#region
umožňuje určit blok kódu, který můžete rozbalit nebo sbalit při použití funkce osnovy editoru kódu. V delších souborech kódu je vhodné sbalit nebo skrýt jednu nebo více oblastí, abyste se mohli soustředit na část souboru, na kterém právě pracujete. Následující příklad ukazuje, jak definovat oblast:
#region MyClass definition
public class MyClass
{
static void Main()
{
}
}
#endregion
Blok #region
musí být ukončen direktivou #endregion
. #region
Blok se nemůže překrývat s blokem#if
. #region
Blok ale může být vnořený do #if
bloku a #if
blok může být vnořený do #region
bloku.
Informace o chybách a upozorněních
Dáváte kompilátoru pokyn, aby generoval chyby a upozornění kompilátoru definované uživatelem a informace řídicího řádku pomocí následujících direktiv:
#error
: Vygenerujte chybu kompilátoru se zadanou zprávou.#warning
: Vygenerujte upozornění kompilátoru s konkrétní zprávou.#line
: Změňte číslo řádku vytištěné se zprávami kompilátoru.
#error
umožňuje vygenerovat uživatelem definovanou chybu CS1029 z konkrétního umístění v kódu. Příklad:
#error Deprecated code in this method.
Poznámka:
Kompilátor zachází #error version
zvláštním způsobem a hlásí chybu kompilátoru CS8304 se zprávou obsahující použitý kompilátor a jazykové verze.
#warning
umožňuje vygenerovat upozornění kompilátoru CS1030 úrovně 1 z konkrétního umístění v kódu. Příklad:
#warning Deprecated code in this method.
#line
umožňuje upravit číslování řádků kompilátoru a (volitelně) výstup názvu souboru pro chyby a upozornění.
Následující příklad ukazuje, jak nahlásit dvě upozornění spojená s čísly řádků. Direktiva #line 200
vynutí, aby číslo dalšího řádku bylo 200 (i když výchozí hodnota je #6) a do další #line
direktivy bude název souboru hlášen jako "Special". Direktiva #line default
vrátí číslování řádků do výchozího číslování, což spočítá řádky, které byly přečíslovány předchozí direktivou.
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;
}
}
Kompilace vytvoří následující výstup:
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
Direktiva #line
se může použít v automatizovaném přechodném kroku procesu sestavení. Pokud byly například řádky odebrány z původního souboru zdrojového kódu, ale přesto jste chtěli, aby kompilátor vygeneroval výstup na základě původního číslování řádků v souboru, mohli byste odebrat řádky a pak simulovat původní číslování řádků s #line
.
Direktiva #line hidden
skryje následující řádky z ladicího programu, takže když vývojář projde kódem, všechny řádky mezi a další #line
direktivou #line hidden
(za předpokladu, že není jinou #line hidden
direktivou), budou stupňovité. Tuto možnost lze použít také k tomu, aby ASP.NET rozlišovala mezi uživatelem definovaným a strojově vygenerovaným kódem. I když ASP.NET je primárním příjemcem této funkce, je pravděpodobné, že ho budou využívat další generátory zdrojů.
Direktiva #line hidden
nemá vliv na názvy souborů ani čísla řádků při hlášení chyb. To znamená, že pokud kompilátor najde chybu ve skrytém bloku, kompilátor oznámí aktuální název souboru a číslo řádku chyby.
Direktiva #line filename
určuje název souboru, který se má zobrazit ve výstupu kompilátoru. Ve výchozím nastavení se používá skutečný název souboru zdrojového kódu. Název souboru musí být v uvozovkách ("") a musí předcházet číslo řádku.
Počínaje jazykem C# 10 můžete použít novou formu direktivy #line
:
#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;
Součásti tohoto formuláře:
(1, 1)
: Počáteční řádek a sloupec prvního znaku na řádku, který následuje za direktivou. V tomto příkladu by se další řádek ohlásil jako řádek 1, sloupec 1.(5, 60)
: Koncový řádek a sloupec pro označenou oblast.10
: Posun sloupce pro direktivu#line
se projeví. V tomto příkladu by byl 10. sloupec nahlášený jako sloupec 1. Tady začíná deklaraceint b = 0;
. Toto pole je nepovinné. Pokud tento parametr vynecháte, direktiva se projeví u prvního sloupce."partial-class.cs"
: Název výstupního souboru.
Předchozí příklad by vygeneroval následující upozornění:
partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used
Po opětovném namapování je proměnná b
, je na prvním řádku, na znaku šest, souboru partial-class.cs
.
Jazyky specifické pro doménu (DSL) obvykle používají tento formát k zajištění lepšího mapování ze zdrojového souboru na vygenerovaný výstup jazyka C#. Nejběžnějším použitím této rozšířené #line
direktivy je přemapování upozornění nebo chyb, které se zobrazí v vygenerovaném souboru do původního zdroje. Představte si například tuto stránku razor:
@page "/"
Time: @DateTime.NowAndThen
Vlastnost DateTime.Now
byla nesprávně zadána jako DateTime.NowAndThen
. Vygenerovaný C# pro tento fragment kódu razor vypadá takto:page.g.cs
_builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
_builder.Add(DateTime.NowAndThen);
Výstup kompilátoru pro předchozí fragment kódu je:
page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'
Na řádku 2, sloupci 6 je místo, kde page.razor
začíná text @DateTime.NowAndThen
. To je uvedeno (2, 6)
ve směrnici. Toto rozpětí @DateTime.NowAndThen
končí na řádku 2, sloupci 27. To je uvedeno (2, 27)
v této směrnici. Text začíná DateTime.NowAndThen
ve sloupci 15 z page.g.cs
. To je uvedeno 15
v této směrnici. Seskupování všech argumentů a kompilátor hlásí chybu v jeho umístění v page.razor
. Vývojář může přejít přímo na chybu ve zdrojovém kódu, nikoli na vygenerovaný zdroj.
Další příklady tohoto formátu najdete ve specifikaci funkce v části s příklady.
Pragmas
#pragma
dává kompilátoru zvláštní pokyny pro kompilaci souboru, ve kterém se zobrazí. Kompilátor musí podporovat pokyny. Jinými slovy, nemůžete použít #pragma
k vytvoření vlastních pokynů k předběžnému zpracování.
#pragma warning
: Povolí nebo zakáže upozornění.#pragma checksum
: Vygenerujte kontrolní součet.
#pragma pragma-name pragma-arguments
Kde pragma-name
je název rozpoznané direktivy pragma a pragma-arguments
je argumenty specifické pro direktivu pragma.
#pragma warning
#pragma warning
může povolit nebo zakázat určitá upozornění.
#pragma warning disable warning-list
#pragma warning restore warning-list
Kde warning-list
je čárkami oddělený seznam čísel upozornění. Předpona CS je volitelná. Pokud nejsou zadána žádná čísla upozornění, disable
zakáže všechna upozornění a restore
povolí všechna upozornění.
Poznámka:
Pokud chcete najít čísla upozornění v sadě Visual Studio, sestavte projekt a v okně Výstup vyhledejte čísla upozornění.
Projeví se disable
na dalším řádku zdrojového souboru. Upozornění se obnoví na řádku za textem restore
. Pokud soubor neobsahuje, restore
upozornění se obnoví do výchozího stavu na prvním řádku všech pozdějších souborů ve stejné kompilaci.
// 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 checksum
Generuje kontrolní součty pro zdrojové soubory, které vám pomůžou s laděním ASP.NET stránek.
#pragma checksum "filename" "{guid}" "checksum bytes"
Kde "filename"
je název souboru, který vyžaduje monitorování změn nebo aktualizací, "{guid}"
je globálně jedinečný identifikátor (GUID) pro algoritmus hash a "checksum_bytes"
je řetězec šestnáctkových číslic představujících bajty kontrolního součtu. Musí to být sudý počet šestnáctkových číslic. Lichý počet číslic má za následek upozornění v době kompilace a direktiva se ignoruje.
Ladicí program sady Visual Studio používá kontrolní součet, aby se zajistilo, že vždy najde správný zdroj. Kompilátor vypočítá kontrolní součet zdrojového souboru a pak vygeneruje výstup do souboru programové databáze (PDB). Ladicí program pak použije pdB k porovnání s kontrolním součtem, který vypočítá pro zdrojový soubor.
Toto řešení nefunguje pro projekty ASP.NET, protože vypočítaný kontrolní součet je určený pro vygenerovaný zdrojový soubor, nikoli pro .aspx soubor. Chcete-li tento problém vyřešit, #pragma checksum
poskytuje podporu kontrolního součtu pro ASP.NET stránky.
Při vytváření projektu ASP.NET v jazyce Visual C# obsahuje vygenerovaný zdrojový soubor kontrolní součet pro .aspx soubor, ze kterého je zdroj generován. Kompilátor pak tyto informace zapíše do souboru PDB.
Pokud kompilátor v souboru nenajde direktivu #pragma checksum
, vypočítá kontrolní součet a zapíše hodnotu do souboru PDB.
class TestClass
{
static int Main()
{
#pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
}
}