2.1.5 C#8.0教程

如果你熟悉 C語言 或它的直系衍生語言,你一定了解預處理器的基本概念。那麼 C# 是否提供了預處理器?結論是:它沒有單獨的預處理階段,也不提供宏。然而,它確實有一些類似於 C語言 預處理器提供的指令,儘管它的選擇非常有限。儘管 C# 不像 C 一樣有完整的預處理階段,但是這些被稱為預處理指令。本課將會提到一些常用的指令。

  1. 編譯符號
  2. #error 與 #warning
  3. #line
  4. #pragma
  5. #nullable
  6. #region and #endregion

編譯符號

C# 提供了一個 #define 指令,可以讓你定義編譯符號。這些符號通常與 #if 指令一起使用,以不同的方式針對不同的情況編譯代碼。例如,你可能希望某些代碼只出現在調試中,或者你可能需要在不同的平臺上使用不同的代碼來實現特定的效果。通常,你不會使用 #define 指令,但更常見的是通過編譯器構建設置來定義編譯符號。要對此進行控制,可以直接打開 .csproj 項目文件,在 PropertyGroup 中的 DefineConstants 元素中定義需要的值。

.NET SDK 定義了某些默認符號。它支持兩種配置:調試Debug 和 發佈Release。它在調試配置中定義 DEBUG 編譯符號,而在發佈中定義了 RELEASE。在兩種配置中定義了一個兩者共有的稱為 TRACE 的符號。某些項目類型使用額外的符號。例如,針對 .NET Standard 2.0 將同時定義NETSTANDARD 和 NETSTANDARD2_0。

編譯符號通常與 #if、#else、#elif 和 #endif 指令一起使用(#elif是else if的縮寫)。例5.1 使用其中一些指令來確保某些代碼行只在調試版本中編譯。(你還可以編寫#if false來完全防止代碼段被編譯。這通常只是作為一種臨時措施,是註釋的替代方法,避免了試圖嵌套註釋時的詞彙陷阱。)

<code>// 示例 5.1
static void Main(string[] args)
{
    #if RELEASE
        Console.WriteLine("RELEASE ...");
    #endif
    #if DEBUG
        Console.WriteLine("DEBUG ...");
    #endif
}/<code>
2.1.5 C#8.0教程 - 預處理指令

圖1. 此時會執行的預編譯指令

c#提供了一種更微妙的機制來支持這類事情,稱為條件方法。編譯器識別.NET類庫定義的稱為ConditionalAttribute的屬性,併為其提供特殊的 編譯時compile-time 行為。你可以給任何方法用此屬性註釋。例5.2 使用它表示只有在定義了調試編譯符號時才應該使用帶註釋的方法。

<code>// 示例 5.2
static void Main(string[] args)
{
    ShowDebugInfo();
    ShowReleaseInfo();
}

[System.Diagnostics.Conditional("DEBUG")]
static void ShowDebugInfo()
{
    Console.WriteLine("Conditional DEBUG ...");
}

[System.Diagnostics.Conditional("RELEASE")]
static void ShowReleaseInfo()
{
    Console.WriteLine("Conditional RELEASE ...");
}/<code>
2.1.5 C#8.0教程 - 預處理指令

圖2. Debug階段輸出結果

2.1.5 C#8.0教程 - 預處理指令

圖3. Release階段輸出結果

注意對比圖2 、圖3的兩個條件方法,一個是在 Debug 階段運行的,一個是在 Release 階段運行的。

特別要注意輸出面板,分別輸出了 “Conditional DEBUG ...” 和 “Conditional RELEASE ...”。說明條件方法在起作用。

另外要特別理解的是,如果您編寫的代碼調用了這樣註釋的方法,c#編譯器將在沒有定義相關符號的編譯中忽略該調用。因此,如果你編寫代碼來調用這兩個 ShowDebugInfo

/ ShowReleaseInfo 方法,編譯器會在編譯中刪除另外一個調用方法,但不會使用指令使代碼混亂。

.NET類庫中的 Debug類 和 Trace類 在System.Diagnostics命名空間使用此特性。Debug類提供了各種以調試編譯符號為條件的方法,而Trace類有以跟蹤為條件的方法。如果保留新C#項目的默認設置,那麼通過Trace類生成的任何診斷輸出都將在調試和發佈版本中可用,但是調用Debug類上的方法的任何代碼都不會編譯到發佈版本中。

#error 與 #warning

C# 允許您使用 #error 和 #warning 指令來選擇生成編譯器錯誤或警告。這些通常在條件區域內使用,如例2-30所示,當然一個無條件執行的 #warning 可以用來提醒自己還沒有編寫一些特別重要的代碼。

<code>// 示例 5.3
static void Main(string[] args)
{
    #warning 當您運行此代碼後,您有權保持沉默,您所說的一切將會作為呈堂證供!
    #error 停止交易,有內鬼!
}/<code>
2.1.5 C#8.0教程 - 預處理指令

圖4. 請注意輸出結果

如圖4.所示,#warning 會導致出現警告提示,但仍會繼續編譯過程。但 #error 除了會出現錯誤提示外,還會導致編譯失敗,或者稱 build 錯誤,修復後才能繼續。

#line

在生成的代碼中,#line指令非常有用。當編譯器生成錯誤或警告時,它通常會聲明問題發生的位置,提供文件名、行號和該行內的偏移量。但是,如果問題代碼是使用其他文件作為輸入自動生成的,並且其他文件包含問題的根本原因,那麼在輸入文件中報告錯誤可能比在生成的文件中報告錯誤更有用。一個#line指令可以指示c#編譯器就像錯誤發生在指定的行號上一樣,也可以選擇像錯誤發生在一個完全不同的文件中一樣。例5.4 展示瞭如何使用它。指令後的錯誤將被報告,好像它來自一個名為footbar.cs的文件的第999行。

<code>static void Main(string[] args)
{
    Inttt x;
    #line 999 "footbar.cs"
    Inttt x;
}/<code>
2.1.5 C#8.0教程 - 預處理指令

圖5. 兩行錯誤,但行號所指不同

如圖5.所示,雖然 Program.cs 的第9行、第11行出現兩處明顯的語法錯誤。但提示出現的文件及所在位置卻明顯不同。請特別關注橙色箭頭與白色箭頭所示的區別。

另外,文件名部分是可選的,行號也可以偽造,一切都只是註釋。您可以通過編寫#line default來告訴編譯器恢復報告正常的警告和錯誤狀態,否則後面出現的一切錯誤都會以這個基礎繼續。

#pragma

#pragma 指令提供了兩個特性:它可以用來禁用選定的編譯器警告,還可以用來覆蓋編譯器放入它生成的包含調試信息的 .pdb 文件中的校驗和值。這兩種方法主要是為代碼生成場景設計的,不過它們有時也可以用於禁用普通代碼中的警告。例5.5展示瞭如何使用 #pragma 來防止您聲明但沒有繼續使用的變量而彈出的警告信息。

<code>static void Main(string[] args)
{
    int x;
    #pragma warning disable CS0168
    int y;
}/<code>
2.1.5 C#8.0教程 - 預處理指令

圖6. 關閉警告後,變量y 不會被提示未使用

如圖6.所示,在啟用 #pragma 指令前,會提示警告變量x未使用。但在啟用後,變量y的警告提示未顯示。

通常應該避免禁用警告。此特性在生成的代碼中非常有用,因為代碼生成通常會創建不經常使用的項,而pragmas可能提供了獲得乾淨編譯的唯一方法。但是當您手工編寫代碼時,通常應該能夠首先避免警告。

總的來說,如果不是強迫症患者,請勿使用該功能。當然,#pragmas 還有一些其他的能力,比如說為 C# 8.0 中添加的新特性 nullable 等。但對於初學者來說,好像暫時無所謂……但畢竟課題是 C# 8.0 教程,所以我們不得不說一些關於 8.0 的新知識。如下:

#nullable

C# 8.0添加了一個新的指令 #nullable,它允許對可空的註釋上下文進行精細控制。這是之後章節中描述的可空引用特性的一部分。(這與之前 #pragmas 中描述的 nullable 警告控件沒有重疊,因為我們控制是否啟用了nullability註釋,與是否啟用了與這些註釋關聯的警告無關)

#region and #endregion

最後,我們有兩個不做任何事情的預處理指令。如果你寫了#region指令,編譯器做的唯一一件事就是確保它們有或者響應#endregion指令。不匹配會導致編譯器錯誤,但是編譯器會忽略正確配對的#region和#endregion指令。區域可以嵌套。

這些指令的存在完全是為了便於選擇識別它們的文本編輯器。Visual Studio 或 Visual Studio Code 等 C#編輯器使用這組指令可將其包含的代碼部分摺疊為屏幕上的一行。c#編輯器自動允許擴展和摺疊某些特性,比如類定義、方法和代碼塊(稱為outline的特性)。如果您使用這兩個指令定義區域,它還將允許擴展和摺疊這些區域。

如果將鼠標懸停在摺疊區域上,Visual Studio Code 等多數C#編輯器將顯示工具提示,顯示該區域的內容。

2.1.5 C#8.0教程 - 預處理指令

圖7. 紅色箭頭所示的 “下拉摺疊按鈕”

如圖7.所示,因為增加了一組 #region 指令,則會在行號後面自動增加出 摺疊按鈕 (紅色箭頭所示),會將從 #region 開始,到 #endregion 結束的代碼塊摺疊起來。

雖然代碼塊 { } 符號也可實現摺疊效果,但區別在於,編譯器會完全忽略 #region,但 { } 對於編譯器來說是有意義的,所以不是完全等同。

本課小結

本課所提到預編譯指令,不能給我們的程序帶來更多的功能,也無法提高代碼執行效率。更多是為代碼編輯者使用。大家瞭解即可,未來讀程序時肯定會遇到,可以再來細讀。

下節開始數據類型……


分享到:


相關文章: