作者: 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
(完)