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
:關閉上述條件式編譯。
只有在已定義指定的符號,或未定義 not 運算子時 !
,C# 編譯器才會編譯 指示詞和 #endif
指示詞之間的 #if
程式碼。 不同于 C 和 C++,無法指派符號的數值。 #if
C# 中的 語句是布林值,而且只會測試是否已定義符號。 例如,定義 時 DEBUG
會編譯下列程式碼:
#if DEBUG
Console.WriteLine("Debug version");
#endif
未定義 時 MYTEST
,會編譯下列程式代碼:
#if !MYTEST
Console.WriteLine("MYTEST is not defined");
#endif
您可以使用運算子==
(相等) ,並!=
(不相等) 來測試 bool
值 true
或 false
。 true
表示已定義符號。 #if DEBUG
陳述式的意義與 #if (DEBUG == true)
一樣。 您可以使用&&
(和) 、 ||
(或) , (!
不) 運算子來評估是否已定義多個符號。 您也可以使用括弧來將符號和運算子分組。
#if
,以及 #else
、 #elif
、 #endif
、 #define
和 #undef
指示詞,可讓您根據一或多個符號的存在來包含或排除程式碼。 在編譯偵錯組建的程式碼或編譯特定組態時,條件式編譯很有用。
開頭為 #if
指示詞的條件指示詞必須明確以 #endif
指示詞終止。 #define
可讓您定義符號。 藉由使用符號做為傳遞至 指示詞的 #if
運算式,運算式會評估為 true
。 您也可以使用 DefineConstants 編譯器選項來定義符號。 您可以使用 取消定義符號 #undef
。 使用 #define
建立的符號範圍是定義它的檔案。 您使用 DefineConstants 或 #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
,編譯器將會評估 與下一個 #endif
之間的 #else
所有程式碼。 #endif
(#endif) 必須是 之後 #else
的下一個預處理器指示詞。
#endif
會指定條件式指示詞的結尾,其開頭為 #if
指示詞。
建置系統也知道預先定義的預處理器符號,代表 SDK 樣式專案中不同的 目標架構 。 建立可以多個 .NET 版本為目標的應用程式時,它們非常有用。
目標 Framework | 符號 | 其他符號 (.NET 5+ SDK) |
僅提供平臺符號 ( 當您指定 OS 特定的 TFM) |
---|---|---|---|
.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 , NET7_0 , NET6_0 , NET5_0 , NETCOREAPP , NETCOREAPP3_1 , NETCOREAPP3_0 , NETCOREAPP2_2 , NETCOREAPP2_1 , NETCOREAPP2_0 , NETCOREAPP1_1 , NETCOREAPP1_0 |
NET7_0_OR_GREATER , NET6_0_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 |
ANDROID , IOS , MACCATALYST , MACOS , TVOS , WINDOWS ,[OS][version] 例如 IOS15_1 ) (,[OS][version]_OR_GREATER 例如 IOS15_1_OR_GREATER , () |
注意
- 不論您要鎖定的版本為何,都會定義無版本符號。
- 版本特定符號只會針對您要鎖定的版本定義。
- 系統會
<framework>_OR_GREATER
針對您要鎖定的版本和所有舊版定義符號。 例如,如果您要以 .NET Framework 2.0 為目標,則會定義下列符號:NET20
、NET20_OR_GREATER
、NET11_OR_GREATER
和NET10_OR_GREATER
。 - 這些與 MSBuild
TargetFramework
屬性 和 NuGet 所使用的目標 Framework Moniker (TPM) 不同。
注意
對於傳統的非 SDK 樣式專案,您必須透過專案的屬性頁面,手動設定 Visual Studio 中不同目標架構的條件式編譯符號。
其他預先定義的符號包括 DEBUG
和 TRACE
常數。 您可以使用 #define
來覆寫為專案所設定的值。 例如,DEBUG 符號會根據您的組建組態屬性 ("Debug" 或 "Release" 模式) 而自動設定。
下列範例示範如何在檔案上定義 MYTEST
符號,然後測試 和 DEBUG
符號的值 MYTEST
。 此範例的輸出取決於您 是否在偵 錯或 發行 組態模式上建置專案。
#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# 中的常數是特別定義為類別或結構的靜態成員。 如果您有數個這類常數,請考慮建立個別的「常數」類別來保留它們。
符號可以用來指定編譯的條件。 您可以使用 或 #elif
測試符號 #if
。 您也可以使用 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 warning
:啟用或停用警告。#pragma checksum
:產生總和檢查碼。
#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
}
}