作者: zyl910
一、原初
.NET平台很早就提供了條件編譯的語法(#if)。
但是當時官方未制定標准的條件編譯符號(Conditional compilation symbols)的名稱,而是讓各程序自行約定。
由於早期只有“.NET Framework”這一種平台,且每次升級都是向下兼容的。沒有標准的標准的預處理器符號名,確實能減少復雜度。
二、混亂期
而到了.NET 4.0、VS2010 的時代,除了“.NET Framework”平台外,還多了“Silverlight”、“XBox”、“Windows Phone 7”等平台。
不久還出現了 PCL(Portable Class Libraries,可移植庫)這樣能在多個平台上的庫。且.NET 開始支持“WinRT/UWP”、“Android”、“iOS”等平台。
此時條件編譯就很重要了,可利用條件編譯對各平台做不同的處理。且有時為了避免編譯失敗或做降級處理,需要判斷平台的版本。
VS2015開始支持共享項目(Shared Project),能在代碼窗口隨時使用下拉框來切換平台版本。對條件編譯的需求越來越大了。
可是由於此時沒有統一的條件編譯符號名稱的約定,大家各自為政。導致代碼的可讀性、可移植性很差。
隨着開源代碼的傳播,符號名稱逐漸形成了一些共識。這一問題稍微有了好轉。
但還有一個更棘手的問題——C#里的條件編譯只能進行布爾(bool)檢查,不支持版本數值比較,導致判斷版本很費勁。
2.1 僅考慮“.NET Standard”平台時的條件判斷
例如有一段代碼,需要在“.NET Standard 1.3”以上環境運行。最開始可以這樣寫條件編譯的判斷:
#if NETSTANDARD1_3
Console.WriteLine("Run on .NET Standard 1.3+");
#endif
注意“.NET Standard”是不斷升級的,目前最新是 2.1版。於是條件編譯的判斷需寫成這樣:
#if (NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 || NETSTANDARD2_0 || NETSTANDARD2_1)
Console.WriteLine("Run on .NET Standard 1.3+");
#endif
若“.NET Standard”以后發布了新版本,那么這個條件編譯判斷得同步修改。
2.2 同時支持“.NET Standard”、“.NET Framework”平台
“.NET Framework”是兼容“.NET Standard”的,常用版本的對應關系是——
- .NET Standard 1.3:.NET Framework 4.6
- .NET Standard 1.4~2.0:.NET Framework 4.6.1
- .NET Standard 2.1:.NET Framework 不支持
例如在“.NET Standard 1.3”上運行的代碼,是能在1.3對應的“.NET Framework 4.6”上運行的。於是條件編譯的判斷改寫成這樣:
#if (NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 || NETSTANDARD2_0 || NETSTANDARD2_1) || (NET46)
Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif
注意“.NET Framework”是不斷升級的,目前最新是 4.8版。於是條件編譯的判斷該寫成這樣:
#if (NETSTANDARD1_3 || NETSTANDARD1_4 || NETSTANDARD1_5 || NETSTANDARD1_6 || NETSTANDARD2_0 || NETSTANDARD2_1) || (NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48)
Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif
若“.NET Standard”、“.NET Framework”任一發布了新版本,那么這個條件編譯判斷得同步修改。
可見,進行多平台的版本判斷,條件編譯會寫的很冗長。而且隨着版本更新,得修改條件加新版本的符號,無法一勞永逸。
三、NET5的統一
到了NET5,官方終於制訂了標准的條件編譯符號。詳見官方文檔《SDK 樣式項目中的目標框架》中的“.NET 目標框架的預處理器符號的完整列表”: https://docs.microsoft.com/zh-cn/dotnet/standard/frameworks#how-to-specify-a-target-framework
- .NET Framework: NETFRAMEWORK, NET48, NET472, NET471, NET47, NET462, NET461, NET46, NET452, NET451, NET45, NET40, NET35, NET20
- .NET Standard: NETSTANDARD, NETSTANDARD2_1, NETSTANDARD2_0, NETSTANDARD1_6, NETSTANDARD1_5, NETSTANDARD1_4, NETSTANDARD1_3, NETSTANDARD1_2, NETSTANDARD1_1, NETSTANDARD1_0
- .NET 5 及更高版本(和 .NET Core): NET, NET6_0, NET5_0, NETCOREAPP, NETCOREAPP3_1, NETCOREAPP3_0, NETCOREAPP2_2, NETCOREAPP2_1, NETCOREAPP2_0, NETCOREAPP1_1, NETCOREAPP1_0
而且官方還貼心給出“_OR_GREATER”后綴的符號,用於簡化版本判斷。例如“NETSTANDARD1_3_OR_GREATER”表示“.NET Standard 1.3”或更高版本。
- .NET Framework: 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: 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): 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
有了這些標准符號后,剛才的條件編譯判斷便可以寫的很簡單了。
#if (NETSTANDARD1_3_OR_GREATER) || (NET46_OR_GREATER)
Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif
四、使用經驗
NET5雖然好,但是有一些舊項目短期內不能升級.NET版本、不能升級VS開發環境。且有時我們需開發支持舊平台的類庫。
此時VS不會像NET5那樣,自動為我們提供標准的條件編譯符號。
於是我們得自立更生,手工配置好條件編譯符號。可參考NET5標准的條件編譯符號,來進行配置,這樣能便於未來的平滑升級。
“_OR_GREATER”后綴的符號雖然好用,但對於手工配置條件編譯符號來說,太麻煩了。故可以不用。
為了簡化配置條件編譯符號的配置,建議僅配置當前版本的符號。例如——
- 對於“.NET Standard 1.3”的項目,僅需配置“NETSTANDARD;NETSTANDARD1_3”。
- 對於“.NET Framework 4.6”的項目,僅需配置“NETFRAMEWORK;NET46”。
由於舊版本的版本號已確定,僅是新版本的版本號無法確定。於是可以考慮反向進行條件編譯判斷,先判斷出不兼容的舊版本,這樣便能一勞永逸,即使版本升級也有效。
“.NET Framework”的歷史很長,若將所有的舊版本都列上,那會太冗長了。考慮到.NET 4.0時代才有多平台概念,故一般情況下,向前兼容只需做到.NET 4.0。有特殊需求時,才考慮支持更舊的版本。
根據這些經驗,剛才的條件編譯判斷,可寫成這樣:
#if (NETSTANDARD1_0 || NETSTANDARD1_1 || NETSTANDARD1_2) || (NET40 || NET45 || NET451 || NET452)
// .NET Standard <=1.2, .NET Framework <=4.5.2
#else
Console.WriteLine("Run on .NET Standard 1.3+, .NET Framework 4.6+");
#endif
(完)