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.");
#elif
    Console.WriteLine("Using older code that doesn't support the above .NET versions.");
#endif

#ifspolu s direktivami #else, , #elif, #endif#definea #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í #undefznaku . 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í truejako , 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, NET48, , NET472, NET47NET462, NET40NET35NET471NET461NET46NET452NET451NET45NET20 NET48_OR_GREATER, NET472_OR_GREATER, , NET471_OR_GREATER, NET462_OR_GREATERNET47_OR_GREATER, NET461_OR_GREATERNET40_OR_GREATERNET452_OR_GREATERNET46_OR_GREATERNET451_OR_GREATERNET45_OR_GREATER, NET35_OR_GREATERNET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, , NETSTANDARD2_0, NETSTANDARD1_5NETSTANDARD1_6, NETSTANDARD1_4NETSTANDARD1_3NETSTANDARD1_2, , NETSTANDARD1_1NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, , NETSTANDARD1_5_OR_GREATERNETSTANDARD1_3_OR_GREATERNETSTANDARD1_2_OR_GREATERNETSTANDARD1_6_OR_GREATERNETSTANDARD1_4_OR_GREATER, , NETSTANDARD1_1_OR_GREATERNETSTANDARD1_0_OR_GREATER
.NET 5+ (a .NET Core) NET, NET8_0, , NET7_0, NET5_0NET6_0, NETCOREAPPNETCOREAPP2_0NETCOREAPP3_0NETCOREAPP3_1NETCOREAPP2_2NETCOREAPP2_1, NETCOREAPP1_1NETCOREAPP1_0 NET8_0_OR_GREATER, NET7_0_OR_GREATER, , NET6_0_OR_GREATER, NETCOREAPP3_1_OR_GREATERNETCOREAPP2_0_OR_GREATERNET5_0_OR_GREATERNETCOREAPP3_0_OR_GREATERNETCOREAPP2_2_OR_GREATERNETCOREAPP2_1_OR_GREATER, NETCOREAPP1_1_OR_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, IOS, , MACOSMACCATALYST, 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_GREATERa NET10_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:

Direktivu #define nelze použít k deklaraci konstantních hodnot, jak je obvykle provedeno v jazyce C a C++. Konstanty v jazyce C# jsou nejlépe definované jako statické členy třídy nebo struktury. 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í #undefznaku .

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á deklarace int 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 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
    }
}