Sdílet prostřednictvím


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 preprocesoru #nullable nastaví příznaky anotací a varování v kontextu neanotovaném. 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ý příznak je zakázán 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í anotace a upozornění a má přednost před nastavením na úrovni projektu. Direktiva nastaví příznak, 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í kontext s možnou hodnotou null zakázáno.
  • #nullable enable: Nastaví kontext s možnou hodnotou null povolenou.
  • #nullable restore: Obnoví kontext s možnou hodnotou null do nastavení projektu.
  • #nullable disable annotations: Nastaví příznak poznámek v kontextu s možnou hodnotou null na zakázáno.
  • #nullable enable annotations: Nastaví příznak poznámek v nulovatelném kontextu na povoleno .
  • #nullable restore annotations: Obnoví příznak poznámek v kontextu s možnou hodnotou null do nastavení projektu.
  • #nullable disable warnings: Nastaví příznak upozornění v kontextu s možnou hodnotou null na zakázáno.
  • #nullable enable warnings: Nastaví příznak upozornění v nulovatelném kontextu na povolenou .
  • #nullable restore warnings: Obnoví příznak upozornění v nullable kontextu v 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.

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, , , NET48NET472NET471NET47NET462NET461NET46NET452NET451NET45NET40NET35NET20 NET48_OR_GREATER, NET472_OR_GREATER, , NET471_OR_GREATER, NET47_OR_GREATERNET462_OR_GREATER, NET461_OR_GREATERNET46_OR_GREATERNET452_OR_GREATERNET451_OR_GREATERNET45_OR_GREATERNET40_OR_GREATER, NET35_OR_GREATERNET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, , NETSTANDARD2_0, NETSTANDARD1_6NETSTANDARD1_5, NETSTANDARD1_4NETSTANDARD1_3NETSTANDARD1_2, , NETSTANDARD1_1NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, , NETSTANDARD1_6_OR_GREATERNETSTANDARD1_5_OR_GREATERNETSTANDARD1_4_OR_GREATERNETSTANDARD1_3_OR_GREATERNETSTANDARD1_2_OR_GREATER, , NETSTANDARD1_1_OR_GREATERNETSTANDARD1_0_OR_GREATER
.NET 5+ (a .NET Core) NET, NET9_0, , NET8_0, NET7_0NET6_0, NET5_0NETCOREAPPNETCOREAPP3_1NETCOREAPP3_0NETCOREAPP2_2NETCOREAPP2_1NETCOREAPP2_0NETCOREAPP1_1NETCOREAPP1_0 NET8_0_OR_GREATER, NET7_0_OR_GREATER, , NET6_0_OR_GREATER, NET5_0_OR_GREATERNETCOREAPP3_1_OR_GREATERNETCOREAPP3_0_OR_GREATERNETCOREAPP2_2_OR_GREATERNETCOREAPP2_1_OR_GREATERNETCOREAPP2_0_OR_GREATER, NETCOREAPP1_1_OR_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, IOS, , MACCATALYSTMACOS, 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).

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 typu Boolean a testuje pouze, zda je symbol definován. 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). Pomocí operátorů && (a), || (nebo)a ! (nikoli) můžete vyhodnotit, jestli jsou definovány více symbolů. Symboly a operátory je také možné seskupovat pomocí závorek.

Následující příklad ukazuje složitou direktivu, 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

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

Výraz #elif umožňuje vytvořit složenou podmíněnou direktivu. Výraz #elif se vyhodnotí, pokud se předchozí #if ani žádné předchozí volitelné výrazy direktivy #elif nevyhodnotí jako 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 .

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. Když 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í #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 dokud další direktiva #line, název souboru bude hlášen jako "Zvláštní". Direktiva #line default vrátí číslování řádků do výchozího číslování, což spočítá řádky přečíslované 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 hidden direktivou #line (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 využívají 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 hlásí 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í následovat za číslem řádku.

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 pro první znak v řádku následujícím 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. Deklarace int b = 0; začíná v daném sloupci. 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é direktivy #line 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'

Řádek 2, sloupec 6 v page.razor je místo, kde začíná text @DateTime.NowAndThen, poznamenáno (2, 6) v direktivě. Rozsah @DateTime.NowAndThen končí na řádku 2, sloupci 27, jak uvádí směrnice (2, 27). Text pro DateTime.NowAndThen začíná ve sloupci 15 na page.g.cs, jak je uvedeno 15 v pokynu. 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 direktivy, které používáte. 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 format a #pragma warning enable format řídí, jak Visual Studio formátuje bloky kódu.

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

Kde warning-list je čárkami oddělený seznam čísel upozornění, například 414, CS3021. 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()
    {
    }
}

Další forma warning direktivy pragma zakáže nebo obnoví příkazy formátování sady Visual Studio v blocích kódu:

#pragma warning disable format
#pragma warning restore format

Příkazy formátu sady Visual Studio neupraví text v blocích kódu, kde se disable format projeví. Příkazy formátování, jako je Ctrl+K, Ctrl+D, tyto oblasti kódu neupravují. Tato direktiva pragma vám poskytne jemnou kontrolu nad prezentací vizuálního vzhledu vašeho kódu.

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