C# 前置處理器指示詞

雖然編譯器沒有另外的前置處理器,但處理本節中所述的指示詞時,會像是有前置處理器一樣。 您可以使用它們來協助進行條件式編譯。 不同于 C 和 c + + 指示詞,您無法使用這些指示詞來建立宏。 每個前置處理器指示詞都必須是該行中的唯一指令。

可為 null 的內容

預處理器指示詞會 #nullable 設定 可為 null 注釋內容可為 null 的警告內容。 這個指示詞會控制可為 null 的注釋是否有效果,以及是否提供可 null 性警告。 每個內容都已 停用啟用

這兩個內容可以在專案層級指定, (在 c # 原始程式碼) 之外。 指示詞 #nullable 會控制注釋和警告內容,並優先于專案層級的設定。 指示詞會設定) 它控制的 (內容,直到另一個指示詞覆寫它,或直到原始程式檔結束為止。

指示詞的效果如下所示:

  • #nullable disable:將可為 null 的注釋和警告內容設定為 停用
  • #nullable enable:將可為 null 的注釋和警告內容設定為 已啟用
  • #nullable restore:將可為 null 的注釋和警告內容還原至專案設定。
  • #nullable disable annotations:將可為 null 注釋內容設定為 停用
  • #nullable enable annotations:將可為 null 注釋內容設定為 已啟用
  • #nullable restore annotations:將可為 null 注釋內容還原至專案設定。
  • #nullable disable warnings:將可為 null 的警告內容設定為 停用
  • #nullable enable warnings:將可為 null 的警告內容設定為 已啟用
  • #nullable restore warnings:將可為 null 警告內容還原至專案設定。

條件式編譯

您可以使用四個預處理器指示詞來控制條件式編譯:

  • #if:開啟條件式編譯,只有在定義指定的符號時,才會編譯器代碼。
  • #elif:關閉上述的條件式編譯,並根據是否已定義指定的符號來開啟新的條件式編譯。
  • #else:如果未定義先前指定的符號,則關閉上述條件式編譯,並開啟新的條件式編譯。
  • #endif:關閉上述的條件式編譯。

當 c # 編譯器找到指示詞,最後是指示詞時 #if #endif ,它只會在定義了指定的符號時,才會在指示詞之間編譯器代碼。 不同于 C 和 c + +,您無法將數值指派給符號。 #ifC # 中的語句是布林值,而且只會測試是否已定義符號。 例如:

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

您可以使用運算子 == (相等) != (不相等) 來測試 booltruefalsetrue 表示已定義符號。 #if DEBUG 陳述式的意義與 #if (DEBUG == true) 一樣。 您可以使用 && (和) || (或) ,以及 ! (不) 運算子來評估是否已定義多個符號。 您也可以使用括弧來將符號和運算子分組。

#if以及 #else 、、、和指示詞,可 #elif #endif #define #undef 讓您根據一或多個符號是否存在來包含或排除程式碼。 在編譯 debug 組建的程式碼時,或針對特定設定編譯時,條件式編譯可能很有用。

以指示詞開頭的條件指示詞 #if 必須以指示詞明確地結束 #endif#define 可讓您定義符號。 藉由使用符號做為傳遞給指示詞的運算式 #if ,運算式會評估為 true 。 您也可以使用 >defineconstants 編譯器選項來定義符號。 您可以使用取消定義符號 #undef 。 使用 #define 建立的符號範圍是定義它的檔案。 您使用 >defineconstants 或 with 定義的符號 #define 不會與相同名稱的變數發生衝突。 也就是說,不應將變數名稱傳遞給預處理器指示詞,而且符號只能由預處理器指示詞來評估。

#elif 可讓您建立複合條件指示詞。 #elif如果上述運算式和先前的選擇性指示詞運算式都不是 #if 評估為,就會評估運算式 #elif true 。 如果 #elif 運算式評估為 true ,則編譯器會評估 #elif 和下一個條件指示詞之間的所有程式碼。 例如:

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

#else 可讓您建立複合條件指示詞,如此一來,如果上述或 (選擇性) 指示詞中的任何運算式都 #if #elif 評估為 true ,則編譯器會評估 #else 和下一個之間的所有程式碼 #endif#endif (#endif) 必須是接下來的預處理器指示詞 #else

#endif 指定以指示詞開頭的條件指示詞結尾 #if

組建系統也會留意表示 SDK 樣式專案中不同 目標 framework 的預先定義預處理器符號。 當您建立的應用程式可能會以多個 .NET 版本為目標時,它們會很有用。

目標 Framework 符號 .NET 5 + SDK 中提供的其他符號
.NET Framework NETFRAMEWORK, 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 + (和 .NET Core) NET, NET6_0, NET6_0_ANDROID, NET6_0_IOS, NET6_0_MACOS, NET6_0_MACCATALYST, NET6_0_TVOS, NET6_0_WINDOWS, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0 NET6_0_OR_GREATER, NET6_0_ANDROID_OR_GREATER, NET6_0_IOS_OR_GREATER, NET6_0_MACOS_OR_GREATER, NET6_0_MACCATALYST_OR_GREATER, NET6_0_TVOS_OR_GREATER, NET6_0_WINDOWS_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

注意

  • 無論您的目標版本為何,都會定義無版本符號。
  • 僅針對您的目標版本定義版本特定的符號。
  • 這些 <framework>_OR_GREATER 符號會針對您的目標版本和所有較早的版本而定義。 例如,如果您的目標是 .NET Framework 2.0,則會定義下列符號: NET_2_0NET_2_0_OR_GREATERNET_1_1_OR_GREATERNET_1_0_OR_GREATER

注意

針對傳統的非 SDK 樣式專案,您必須透過專案的 [屬性] 頁面,在 Visual Studio 中手動設定不同目標 framework 的條件式編譯符號。

其他預先定義的符號包含 DEBUGTRACE 常數。 您可以使用 #define 來覆寫為專案所設定的值。 例如,DEBUG 符號會根據您的組建組態屬性 ("Debug" 或 "Release" 模式) 而自動設定。

下列範例顯示如何 MYTEST 在檔案上定義符號,然後測試 MYTEST 和符號的值 DEBUG 。 此範例的輸出取決於您是在 DebugRelease configuration 模式上建立專案。

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

以下範例說明如何測試不同的目標 Framework,讓您可以盡可能使用較新的 API:

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

定義符號

您可以使用下列兩個預處理器指示詞來定義或取消定義條件式編譯的符號:

  • #define:定義符號。
  • #undef:取消定義符號。

您可以使用 #define 來定義符號。 當您使用符號作為傳遞至指示詞的運算式時 #if ,運算式會評估為 true ,如下列範例所示:

#define VERBOSE

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

注意

如果常數值通常是在 C 和 C++ 中進行宣告,您就不能使用 #define 指示詞進行宣告。 在 C# 中的常數是特別定義為類別或結構的靜態成員。 如果您有數個這類常數,請考慮建立個別的「常數」類別來保留它們。

符號可以用來指定編譯的條件。 您可以使用或來測試符號 #if #elif 。 您也可以使用 ConditionalAttribute 執行條件式編譯。 您可以定義符號,但無法指派值給符號。 如果您要使用的任何指示並不是前置處理器指示詞,則檔案中必須先出現 #define 指示詞才行。 您也可以使用 >defineconstants 編譯器選項來定義符號。 您可以使用取消定義符號 #undef

定義區域

您可以使用下列兩個預處理器指示詞,定義可在大綱中折迭的程式碼區域:

  • #region:啟動區域。
  • #endregion:結束區域。

#region 當您使用程式碼編輯器的 大綱 功能時,可讓您指定可展開或折迭的程式碼區塊。 在較長的程式碼檔案中,您可以輕鬆地折迭或隱藏一或多個區域,讓您可以將焦點放在目前正在處理的檔案部分。 下例示範如何定義區域:

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

#region區塊必須以指示詞終止 #endregion#region區塊無法與區塊重迭 #if 。 不過,區塊 #region 可以嵌套在 #if 區塊中,而且區塊 #if 可以嵌套在 #region 區塊中。

錯誤和警告資訊

您可以指示編譯器產生使用者定義的編譯器錯誤和警告,並使用下列指示詞來控制行資訊:

  • #error:使用指定的訊息產生編譯器錯誤。
  • #warning:產生具有特定訊息的編譯器警告。
  • #line:變更以編譯器訊息列印的行號。

#error 可讓您從您程式碼中的特定位置產生 CS1029 使用者定義錯誤。 例如:

#error Deprecated code in this method.

注意

編譯器會 #error version 以特殊方式處理,並使用包含已使用之編譯器和語言版本的訊息來報告編譯器錯誤 CS8304。

#warning 可讓您從程式碼中的特定位置產生 CS1030 層級一的編譯器警告。 例如:

#warning Deprecated code in this method.

#line 可讓您修改編譯器的行號以及 (選擇性) 錯誤和警告的檔案名稱輸出。

下列範例示範如何報告兩個與行號建立關聯的警告。 #line 200 指示詞會將下一行的行號強制為 200 (但預設值為 #6),而且在下一個 #line 指示詞之前,檔案名稱將會回報為 "Special"。 #line default 指示詞會將行編號還原為其預設編號,這會計算已由先前的指示詞重新編號的行。

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

編譯會產生下列輸出:

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

#line 指示詞可以用於建置程序中的自動化中繼步驟。 例如,如果已從原始程式碼檔中移除行,但您仍然想要編譯器根據檔案中的原始行編號來產生輸出,則可以移除行,然後模擬具有 #line 的原始行編號。

#line hidden指示詞會隱藏偵錯工具中的後續程式程式碼,如此一來,當開發人員逐步執行程式碼時,和下一個指示詞之間的任何一行, #line hidden #line (假設它不是其他指示詞 #line hidden) 將會進行。 此選項也可用來讓 ASP.NET 區分使用者定義的程式碼與電腦產生的程式碼。 雖然 ASP.NET 是這項功能的主要取用者,但很可能會有更多來源產生器使用它。

指示詞 #line hidden 不會影響錯誤報表中的檔案名或行號。 也就是說,如果編譯器在隱藏區塊中發現錯誤,則編譯器會報告錯誤的目前檔案名和行號。

#line filename 指示詞指定您想要在編譯器輸出中顯示的檔案名稱。 預設會使用原始程式碼檔的實際名稱。 檔案名稱必須以雙引號 ("") 括住,而且前面必須有行號。

從 c # 10 開始,您可以使用新形式的指示詞 #line

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

此表單的元件包括:

  • (1, 1):指示詞後面的行第一個字元的起始行和欄。 在此範例中,下一行會回報為第1行(第1欄)。
  • (5, 60):標示之區域的結束列和資料行。
  • 10:要使指示詞生效的資料行位移 #line 。 在此範例中,第10個數據行會回報為第一個資料行。 這就是宣告的 int b = 0; 開始位置。 這是選擇性欄位。 如果省略,指示詞會在第一個資料行上生效。
  • "partial-class.g.cs":輸出檔的名稱。

上述範例會產生下列警告:

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

重新對應之後,變數 b 就會在第一行,第六個字元。

特定領域語言 (Dsl) 通常會使用這種格式,從來源檔案提供更好的對應至產生的 c # 輸出。 若要查看更多此格式的範例,請參閱有關範例的一節中的 功能規格

Pragma

#pragma 將編譯編譯器所在檔案的特殊指示提供給編譯器。 編譯器必須支援指示。 換句話說,您無法使用 #pragma 來建立自訂前置處理指示。

#pragma pragma-name pragma-arguments

其中 pragma-name 是可辨識 pragma 的名稱,而 pragma-arguments 是 pragma 特定的引數。

#pragma warning

#pragma warning 可以啟用或停用特定警告。

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

其中 warning-list 是以逗號分隔的警告編號清單。 "CS" 前置詞是選擇性的。 未指定警告編號時,disable 會停用所有警告,而 restore 會啟用所有警告。

注意

若要尋找 Visual Studio 中的警告編號,請建立專案,然後在 [輸出] 視窗中尋找警告編號。

disable會從原始程式檔的下一行開始生效。 警告會在後面的那一行還原 restore 。 如果檔案中沒有 restore ,則會在相同編譯中的任何後續檔案的第一行將警告還原為其預設狀態。

// 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

產生原始程式檔的總和檢查碼,協助偵錯 ASP.NET 頁面。

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

其中 "filename" 是需要監視變更或更新的檔案名, "{guid}" 是雜湊演算法 (GUID) 的全域唯一識別碼,而且 "checksum_bytes" 是十六進位數位的字串,代表總和檢查碼的位元組。 必須是偶數的十六進位數字。 奇數的數位會導致編譯時期警告,並且忽略指示詞。

Visual Studio 偵錯工具會使用總和檢查碼來確定一定會找到正確的來源。 編譯器會計算來源檔案的總和檢查碼,然後將輸出發至程式資料庫 (PDB) 檔案。 然後偵錯工具會使用 PDB 比較總和檢查碼計算來源檔案。

此方案不適用於 ASP.NET 的專案,因為計算的總和檢查碼適用于產生的原始程式檔,而不是 .aspx 檔案。 若要解決這個問題,#pragma checksum 會為 ASP.NET 頁面提供總和檢查碼支援。

當您在 Visual C# 中建立 ASP.NET 專案時,所產生原始程式檔會包含來源 .aspx 檔案的總和檢查碼。 接著編譯器會將這項資訊寫入 PDB 檔案中。

如果編譯器在檔案中找不到 #pragma checksum 指示詞,它會計算總和檢查碼並將值寫入 PDB 檔案。

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