C# előfeldolgozási irányelvek

Bár a fordító nem rendelkezik külön előfeldolgozóval, az ebben a szakaszban leírt irányelvek úgy vannak feldolgozva, mintha lenne ilyen. Ezek használatával segíthet a feltételes fordításban. A C és C++ direktíváktól eltérően nem használhatja ezeket az irányelveket makrók létrehozásához. Az előfeldolgozó direktívának kell lennie az egyetlen utasításnak egy sorban.

Null értékű környezet

Az #nullable előfeldolgozási irányelv beállítja a null értékű széljegyzetkörnyezetet és a null értékű figyelmeztetési környezetet. Ez az irányelv szabályozza, hogy a null értékű széljegyzetek érvénybe lépnek-e, és hogy vannak-e null értékű figyelmeztetések. Minden környezet le van tiltva vagy engedélyezve van.

Mindkét környezet megadható a projekt szintjén (a C#-forráskódon kívül), és hozzáadja az Nullable elemet az PropertyGroup elemhez. Az #nullable irányelv szabályozza a széljegyzeteket és a figyelmeztetési környezeteket, és elsőbbséget élvez a projektszintű beállításokkal szemben. Egy irányelv beállítja az általa vezérlő környezet(ek)et, amíg egy másik irányelv felül nem bírálja azt, vagy a forrásfájl végéig.

Az irányelvek hatása a következő:

  • #nullable disable: A null értékű széljegyzeteket és figyelmeztetési környezeteket letiltottra állítja.
  • #nullable enable: A null értékű széljegyzeteket és a figyelmeztető környezeteket engedélyezve állítja be.
  • #nullable restore: Visszaállítja a null értékű széljegyzeteket és a figyelmeztető környezeteket a projektbeállításokra.
  • #nullable disable annotations: A null értékű széljegyzetkörnyezetet letiltottra állítja.
  • #nullable enable annotations: A null értékű széljegyzetkörnyezetet engedélyezve állítja be.
  • #nullable restore annotations: Visszaállítja a null értékű széljegyzetkörnyezetet a projektbeállításokra.
  • #nullable disable warnings: Letiltásra állítja a null értékű figyelmeztetési környezetet.
  • #nullable enable warnings: A null értékű figyelmeztetési környezetet engedélyezve állítja be.
  • #nullable restore warnings: Visszaállítja a null értékű figyelmeztető környezetet a projektbeállításokra.

Feltételes fordítás

A feltételes fordítás szabályozásához négy előprocesszor-direktívát használ:

  • #if: Megnyitja a feltételes fordítást, ahol a kód csak a megadott szimbólum definiálása esetén lesz lefordítva.
  • #elif: Bezárja az előző feltételes fordítást, és megnyit egy új feltételes fordítást a megadott szimbólum megadása alapján.
  • #else: Bezárja az előző feltételes fordítást, és új feltételes fordítást nyit meg, ha az előző megadott szimbólum nincs definiálva.
  • #endif: Bezárja az előző feltételes fordítást.

A C#-fordító csak akkor fordítja le a kódot az irányelv és #endif az #if irányelv között, ha a megadott szimbólum definiálva van, vagy nincs definiálva a ! nem operátor használatakor. A C és a C++ függvénytől eltérően egy szimbólumhoz tartozó numerikus érték nem rendelhető hozzá. A #if C# utasítás logikai érték, és csak azt ellenőrzi, hogy a szimbólum definiálva van-e. A rendszer például a következő kódot fordítja le, amikor DEBUG meg van adva:

#if DEBUG
    Console.WriteLine("Debug version");
#endif

A rendszer a következő kódot fordítja le, ha MYTEST nincs definiálva:

#if !MYTEST
    Console.WriteLine("MYTEST is not defined");
#endif

Az értékeket az operátorok == (egyenlőség) és != az (egyenlőtlenség) segítségével tesztelheti.booltruefalse true azt jelenti, hogy a szimbólum definiálva van. Az utasítás #if DEBUG jelentése megegyezik a .#if (DEBUG == true) A (és),||(vagy) és ! (nem) operátorokkal && kiértékelheti, hogy több szimbólum van-e definiálva. A szimbólumokat és operátorokat zárójelekkel is csoportosíthatja.

Az alábbi összetett irányelv lehetővé teszi, hogy a kód kihasználja az újabb .NET-funkciókat, miközben visszamenőlegesen kompatibilis marad. Tegyük fel például, hogy NuGet-csomagot használ a kódban, de a csomag csak a .NET 6-os és újabb verziót, valamint a .NET Standard 2.0-s és újabb verziót támogatja:

#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

#if#elseaz , , #define#elif#endifés #undef irányelvek mellett egy vagy több szimbólum meglétén alapuló kód belefoglalását vagy kizárását is lehetővé teszi. A feltételes fordítás hasznos lehet egy hibakeresési build kódjának összeállításakor vagy egy adott konfiguráció összeállításakor.

Az irányelvvel #if kezdődő feltételes irányelveket kifejezetten meg kell szüntetni egy #endif irányelvvel. #define lehetővé teszi egy szimbólum definiálásához. Ha a szimbólumot az irányelvnek #if átadott kifejezésként használja, a kifejezés kiértékeli a következőt true: . A DefineConstants fordítóbeállítással szimbólumot is definiálhat. A szimbólumok nem módosíthatók.#undef A szimbólumok #define hatóköre az a fájl, amelyben definiálták. A DefineConstants használatával vagy azzal #define definiált szimbólum nem ütközik egy azonos nevű változóval. Vagyis egy változó nevét nem szabad átadni egy előfeldolgozó-irányelvnek, és a szimbólumok csak előfeldolgozási direktívával értékelhetők ki.

#elif lehetővé teszi összetett feltételes irányelv létrehozását. A #elif kifejezés akkor lesz kiértékelve, ha sem az előző #if , sem az előző, nem kötelező, #elif irányelvkifejezések nem értékelik ki a következőt true: . Ha egy #elif kifejezés kiértékelése trueígy történik, a fordító kiértékeli a következő feltételes irányelv és a #elif következő feltételes direktíva közötti összes kódot. Példa:

#define VC7
//...
#if DEBUG
    Console.WriteLine("Debug build");
#elif VC7
    Console.WriteLine("Visual Studio 7");
#endif

#else lehetővé teszi egy összetett feltételes direktíva létrehozását, így ha az előző #if vagy (nem kötelező) #elif irányelvek egyik kifejezése sem értékelhető ki true, a fordító kiértékeli az összes kódot a következő és a #else között #endif. #endif(#endif) után a következő előfeldolgozó irányelvnek #elsekell lennie.

#endif meghatározza az irányelvvel #if kezdődő feltételes irányelv végét.

A buildrendszer az előre definiált előfeldolgozó szimbólumokkal is tisztában van, amelyek az SDK-stílusú projektek különböző cél-keretrendszereit jelölik. Olyan alkalmazások létrehozásakor hasznosak, amelyek több .NET-verziót is megcélzhatnak.

Cél-keretrendszerek Szimbólumok További szimbólumok
(.NET 5+ SDK-kban érhető el)
Platformszimbólumok (csak
operációsrendszer-specifikus TFM megadásakor)
.NET-keretrendszer NETFRAMEWORK, NET48, NET472, NET471, NET47NET462, NET461, NET46, NET452, NET451, NET45, NET40, , NET35,NET20 NET48_OR_GREATER, NET472_OR_GREATER, NET471_OR_GREATER, NET47_OR_GREATER, NET462_OR_GREATERNET461_OR_GREATER, NET46_OR_GREATER, NET452_OR_GREATER, NET451_OR_GREATER, NET45_OR_GREATER, NET40_OR_GREATER, , , NET35_OR_GREATERNET20_OR_GREATER
.NET Standard NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, , NETSTANDARD1_1NETSTANDARD1_0 NETSTANDARD2_1_OR_GREATER, NETSTANDARD2_0_OR_GREATER, NETSTANDARD1_6_OR_GREATER, NETSTANDARD1_5_OR_GREATERNETSTANDARD1_4_OR_GREATER, NETSTANDARD1_3_OR_GREATER, NETSTANDARD1_2_OR_GREATER, , NETSTANDARD1_1_OR_GREATERNETSTANDARD1_0_OR_GREATER
.NET 5+ (és .NET Core) NET, NET8_0, NET7_0, NET6_0, NET5_0NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, , , NETCOREAPP1_1NETCOREAPP1_0 NET8_0_OR_GREATER, NET7_0_OR_GREATER, NET6_0_OR_GREATER, NET5_0_OR_GREATER, NETCOREAPP3_1_OR_GREATERNETCOREAPP3_0_OR_GREATER, NETCOREAPP2_2_OR_GREATER, NETCOREAPP2_1_OR_GREATER, NETCOREAPP2_0_OR_GREATER, , NETCOREAPP1_1_OR_GREATERNETCOREAPP1_0_OR_GREATER ANDROID, BROWSER, IOS, MACCATALYSTMACOS, TVOS, WINDOWS
[OS][version] (például IOS15_1),
[OS][version]_OR_GREATER (például IOS15_1_OR_GREATER)

Feljegyzés

  • A verzió nélküli szimbólumok a megcélzott verziótól függetlenül vannak definiálva.
  • A verzióspecifikus szimbólumok csak a megcélzott verzióhoz vannak definiálva.
  • A <framework>_OR_GREATER szimbólumok a megcélzott verzióhoz és az összes korábbi verzióhoz vannak definiálva. Ha például a 2.0-s .NET-keretrendszer céloz meg, a következő szimbólumok vannak definiálva: NET20, NET20_OR_GREATER, NET11_OR_GREATERés NET10_OR_GREATER.
  • A NETSTANDARD<x>_<y>_OR_GREATER szimbólumok csak a .NET Standard célokhoz vannak definiálva, a .NET Standardot implementáló célokhoz nem, például a .NET Core-hoz és a .NET-keretrendszer.
  • Ezek eltérnek az MSBuild TargetFramework tulajdonság és a NuGet által használt célkeret-monikerektől (TFM-ek).

Feljegyzés

Hagyományos, nem SDK stílusú projektek esetén manuálisan kell konfigurálnia a Visual Studio különböző cél-keretrendszereihez tartozó feltételes fordítási szimbólumokat a projekt tulajdonságlapjain keresztül.

Más előre definiált szimbólumok közé tartoznak a konstansok és TRACE az DEBUG állandók. A projekthez beállított értékeket felülbírálhatja a következővel #define: . A HIBAKERESÉS szimbólum például automatikusan be van állítva a buildkonfiguráció tulajdonságaitól ("Hibakeresés" vagy "Kiadás" módtól függően).

Az alábbi példa bemutatja, hogyan definiálhat szimbólumokat egy MYTEST fájlban, majd tesztelheti a szimbólumok és DEBUG szimbólumok MYTEST értékeit. A példa kimenete attól függ, hogy a projektet hibakeresési vagy kiadási konfigurációs módra építette-e.

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

Az alábbi példa bemutatja, hogyan tesztelheti a különböző cél keretrendszereket, hogy lehetőség szerint újabb API-kat használjon:

public class MyClass
{
    static void Main()
    {
#if NET40
        WebClient _client = new WebClient();
#else
        HttpClient _client = new HttpClient();
#endif
    }
    //...
}

Szimbólumok definiálása

A feltételes fordításhoz a következő két előfeldolgozási direktívát használhatja szimbólumok definiálásához vagy nem definiálásához:

  • #define: Adjon meg egy szimbólumot.
  • #undef: Jel jelének megjelölése.

Szimbólum meghatározására használható #define . Ha a szimbólumot használja az irányelvnek #if átadott kifejezésként, a kifejezés kiértékelése truea következő példa szerint történik:

#define VERBOSE

#if VERBOSE
   Console.WriteLine("Verbose output version");
#endif

Feljegyzés

Az #define irányelv nem használható állandó értékek deklarálására, ahogyan az általában C és C++ nyelven történik. A C# állandói az osztály vagy a szerkezet statikus tagjaiként vannak a legjobban definiálva. Ha több ilyen állandóval rendelkezik, érdemes lehet külön "Állandók" osztályt létrehozni a megtartásukhoz.

A szimbólumok a fordítás feltételeinek megadására használhatók. A szimbólumot a következővel #if is tesztelheti: vagy #elif. A feltételes fordítást is használhatja ConditionalAttribute . Szimbólumot definiálhat, de nem rendelhet értéket szimbólumhoz. Az #define irányelvnek meg kell jelennie a fájlban, mielőtt olyan utasításokat használ, amelyek nem is előfeldolgozási irányelvek. A DefineConstants fordítóbeállítással szimbólumot is definiálhat. A szimbólumok nem módosíthatók.#undef

Régiók meghatározása

A kódterületeket a következő két előprocesszor-direktíva használatával lehet tagolásban összecsukni:

  • #region: Indítsa el a régiót.
  • #endregion: Egy régió befejezése.

#region lehetővé teszi egy kódblokk megadását, amelyet kibonthat vagy összecsukhat a kódszerkesztő vázlatos funkciójának használatakor. Hosszabb kódfájlok esetén célszerű összecsukni vagy elrejteni egy vagy több régiót, hogy a jelenleg használt fájlrészre összpontosíthasson. Az alábbi példa bemutatja, hogyan definiálhat régiót:

#region MyClass definition
public class MyClass
{
    static void Main()
    {
    }
}
#endregion

A #region blokkokat egy #endregion irányelvvel kell megszüntetni. A #region blokkok nem fedhetik át a blokkokat #if . A blokkok #region azonban beágyazhatók egy #if blokkba, és egy #if blokk beágyazható egy #region blokkba.

Hiba- és figyelmeztetési információk

Arra utasítja a fordítót, hogy hozzon létre felhasználó által definiált fordítóhibákat és figyelmeztetéseket, és a következő irányelvek használatával szabályozza a sorinformációkat:

  • #error: Fordítóhiba létrehozása egy megadott üzenettel.
  • #warning: Állítson elő egy fordító figyelmeztetést egy adott üzenettel.
  • #line: Módosítsa a fordítóüzenetekkel nyomtatott sorszámot.

#errorlehetővé teszi, hogy cs1029 felhasználó által megadott hibát generáljon a kód egy adott helyről. Például:

#error Deprecated code in this method.

Feljegyzés

A fordító speciális módon kezeli #error version , és egy CS8304 nevű fordítóhibát jelent a használt fordítót és nyelvi verziót tartalmazó üzenettel.

#warning lehetővé teszi egy CS1030 szintű fordítói figyelmeztetés generálása a kód egy adott helyről. Példa:

#warning Deprecated code in this method.

#line lehetővé teszi a fordító sorszámozásának módosítását, és (opcionálisan) a fájlnév kimenetét hibák és figyelmeztetések esetén.

Az alábbi példa bemutatja, hogyan jelenthet két, sorszámhoz társított figyelmeztetést. Az #line 200 irányelv arra kényszeríti a következő sor számát, hogy 200 legyen (bár az alapértelmezett érték a 6. szám), és a következő #line irányelvig a fájlnév "Speciális" néven jelenik meg. Az #line default irányelv a sorszámozást az alapértelmezett számozásra adja vissza, amely megszámolja az előző irányelv által újraszámozott sorokat.

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

A fordítás a következő kimenetet hozza létre:

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

Az #line irányelv a buildelési folyamat egy automatizált, köztes lépésében használható. Ha például a sorok el lettek távolítva az eredeti forráskódfájlból, de továbbra is azt szeretné, hogy a fordító kimenetet hozzon létre a fájl eredeti sorszámozása alapján, eltávolíthatja a sorokat, majd szimulálhatja az eredeti sor számozását #line.

Az #line hidden irányelv elrejti az egymást követő sorokat a hibakereső elől, így amikor a fejlesztő végiglép a kódon, az egyik #line hidden és a következő #line irányelv (feltételezve, hogy nem egy másik #line hidden irányelv) közötti sorokat átlépteti. Ez a beállítás lehetővé teszi, hogy ASP.NET különbséget tegyen a felhasználó által definiált és a gép által létrehozott kód között. Bár ASP.NET a szolgáltatás elsődleges felhasználója, valószínű, hogy több forrásgenerátor fogja használni.

Az #line hidden irányelv nem befolyásolja a fájlneveket és a sorszámokat a hibajelentésben. Ez azt jelenti, hogy ha a fordító hibát talál egy rejtett blokkban, a fordító a hiba aktuális fájlnevét és sorszámát jelenti.

Az #line filename irányelv meghatározza a fordító kimenetében megjeleníteni kívánt fájlnevet. Alapértelmezés szerint a forráskódfájl tényleges nevét használja a rendszer. A fájlnévnek idézőjelekben ("") kell lennie, és egy sorszámmal kell megelőznie.

A C# 10-től kezdve használhatja az irányelv új formáját #line :

#line (1, 1) - (5, 60) 10 "partial-class.cs"
/*34567*/int b = 0;

Az űrlap összetevői a következők:

  • (1, 1): Az irányelv szerinti sor első karakterének kezdővonala és oszlopa. Ebben a példában a következő sort 1. sorként, 1. oszlopként kell jelenteni.
  • (5, 60): A megjelölt régió záró sora és oszlopa.
  • 10: Az irányelv érvénybe lépő oszlopeltolása #line . Ebben a példában a 10. oszlop lesz az első oszlop. Itt kezdődik a deklaráció int b = 0; . A mező nem kötelező. Ha nincs megadva, az irányelv az első oszlopra lép érvénybe.
  • "partial-class.cs": A kimeneti fájl neve.

Az előző példa a következő figyelmeztetést generálja:

partial-class.cs(1,5,1,6): warning CS0219: The variable 'b' is assigned but its value is never used

Az újrakészítés után a változó a bfájl partial-class.cselső sorában, a 6. karakternél található.

A tartományspecifikus nyelvek (DSL-k) általában ezt a formátumot használják, hogy jobb leképezést biztosítsanak a forrásfájlból a létrehozott C#-kimenetre. Ennek a kiterjesztett #line irányelvnek a leggyakoribb használata a generált fájlban megjelenő figyelmeztetések vagy hibák újbóli leképezése az eredeti forrásra. Vegyük például ezt a borotvalapot:

@page "/"
Time: @DateTime.NowAndThen

A tulajdonság DateTime.Now helytelenül lett begépelve.DateTime.NowAndThen A razor-kódrészlethez létrehozott C# a következőhöz hasonlóan néz ki:page.g.cs

  _builder.Add("Time: ");
#line (2, 6) - (2, 27) 15 "page.razor"
  _builder.Add(DateTime.NowAndThen);

Az előző kódrészlet fordítói kimenete a következő:

page.razor(2, 2, 2, 27)error CS0117: 'DateTime' does not contain a definition for 'NowAndThen'

A 2. sor 6. oszlopa page.razor a szöveg @DateTime.NowAndThen kezdőbetűje. Ezt az irányelv is megjegyzi (2, 6) . @DateTime.NowAndThen A 2. sor végei, 27. oszlop. Ezt az irányelv is megjegyzi (2, 27) . A kezdő szöveg az DateTime.NowAndThen alábbi 15 page.g.cs. oszlopban kezdődik: Ezt az irányelv is megjegyzi 15 . Az összes argumentum összeadása, és a fordító jelenti a hibát a helyében.page.razor A fejlesztő közvetlenül a forráskódban szereplő hibára tud navigálni, nem pedig a létrehozott forrásra.

Ha további példákat szeretne látni erre a formátumra, tekintse meg a példákról szóló szakaszban található funkcióspecifikációt .

Pragmas

#pragma speciális utasításokat ad a fordítónak annak a fájlnak a fordításához, amelyben megjelenik. Az utasításokat a fordítónak támogatnia kell. Más szóval nem hozhat #pragma létre egyéni előfeldolgozási utasításokat.

#pragma pragma-name pragma-arguments

Hol pragma-name található egy felismert pragma neve, és pragma-arguments hol vannak a pragma-specifikus argumentumok.

#pragma figyelmeztetés

#pragma warning bizonyos figyelmeztetéseket engedélyezhet vagy letilthat.

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

Hol warning-list található a figyelmeztető számok vesszővel tagolt listája? A "CS" előtag nem kötelező. Ha nincs megadva figyelmeztetési szám, letiltja az összes figyelmeztetést, disable és restore engedélyezi az összes figyelmeztetést.

Feljegyzés

Ha figyelmeztető számokat szeretne keresni a Visual Studióban, hozza létre a projektet, majd keresse meg a figyelmeztető számokat a Kimenet ablakban.

A disable művelet a forrásfájl következő sorától kezdve lép érvénybe. A figyelmeztetés a következő sorban restorelesz visszaállítva: . Ha nem restore szerepel a fájlban, a rendszer visszaállítja a figyelmeztetéseket az alapértelmezett állapotba az ugyanabban a fordításban lévő későbbi fájlok első sorában.

// 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 ellenőrzőösszeg

Ellenőrzőösszegeket hoz létre a forrásfájlokhoz ASP.NET lapok hibakereséséhez.

#pragma checksum "filename" "{guid}" "checksum bytes"

A "filename" módosítások vagy frissítések "{guid}" figyelését igénylő fájl neve a kivonatoló algoritmus globálisan egyedi azonosítója (GUID), és "checksum_bytes" az ellenőrzőösszeg bájtjait képviselő hexadecimális számjegyek sztringje. Páros számú hexadecimális számjegynek kell lennie. A páratlan számú számjegy fordítási időt jelző figyelmeztetést eredményez, és a rendszer figyelmen kívül hagyja az irányelvet.

A Visual Studio hibakeresője ellenőrzőösszeget használ annak ellenőrzéséhez, hogy mindig a megfelelő forrást találja-e meg. A fordító kiszámítja egy forrásfájl ellenőrzőösszegét, majd kibocsátja a kimenetet a programadatbázis (PDB) fájlba. A hibakereső ezután a PDB használatával összehasonlítja a forrásfájlhoz kiszámított ellenőrzőösszeget.

Ez a megoldás nem működik ASP.NET projektek esetében, mert a kiszámított ellenőrzőösszeg a létrehozott forrásfájlhoz tartozik, nem pedig a .aspx fájlhoz. A probléma #pragma checksum megoldásához ellenőrzőösszeg-támogatást biztosít ASP.NET lapokhoz.

Amikor ASP.NET projektet hoz létre a Visual C#-ban, a létrehozott forrásfájl egy ellenőrzőösszeget tartalmaz a .aspx fájlhoz, amelyből a forrás létrejön. A fordító ezután beírja ezeket az adatokat a PDB-fájlba.

Ha a fordító nem talál utasítást #pragma checksum a fájlban, kiszámítja az ellenőrzőösszeget, és az értéket a PDB-fájlba írja.

class TestClass
{
    static int Main()
    {
        #pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
    }
}