.NET Standard 来日苦短去日长( 三 )
我们统一了 .NET 平台 , 在它们下面又增加了一个合成平台 , 代表了通用的 API 集 。 从很现实的意义上来说 , 这幅漫画是很到位的表达了这个痛点:
文章插图
如果不能实现真正意义上的合并 , 我们就无法解决这个问题 , 这正是 .NET 5 所做的:它提供了一个统一的实现 , 各方都建立在相同的基础上 , 从而得到相同的 API 和版本号 。
问题 3:.NET Standard 公开了特定平台 API当我们设计 .NET Standard 时 , 为了避免过多地破坏库的生态系统 , 我们不得不做出让步[4] 。 也就是说 , 我们不得不包含一些 Windows 专用的 API(如文件系统 ACL、注册表、WMI 等) 。 今后 , 我们将避免在 net5.0、net6.0 和未来的版本中加入特定平台的 API 。 然而 , 我们不可能预测未来 。 例如 , 我们最近为 Blazor WebAssembly 增加了一个新的 .NET 运行环境 , 在这个环境中 , 一些原本跨平台的 API(如线程或进程控制)无法在浏览器的沙箱中得到支持 。
很多人抱怨说 , 这类 API 感觉就像“地雷”--代码编译时没有错误 , 因此看起来可以移植到任何平台上 , 但当运行在一个没有给定 API 实现的平台上时 , 就会出现运行时错误 。
从 .NET 5 开始 , 我们将提供随 SDK 发布的默认开启的分析器和代码修复器 。 它包含平台兼容性分析器 , 可以检测无意中使用了目标平台并不支持的 API 。 这个功能取代了 Microsoft.DotNet.Analyzers.Compatibility NuGet 包 。
让我们先来看看 Windows 特有的 API 。
处理 Windows 特定 API
当你创建一个 Target 为 net5.0 为目标的项目时 , 你可以引用 Microsoft.Win32.Registry 包 。 但当你开始使用它时:
private static string GetLoggingDirectory(){using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Fabrikam")){if (key?.GetValue("LoggingDirectoryPath") is string configuredPath)return configuredPath;}string exePath = Process.GetCurrentProcess().MainModule.FileName;string folder = Path.GetDirectoryName(exePath);return Path.Combine(folder, "Logging");}
你会得到以下警告:
CA1416: 'RegistryKey.OpenSubKey(string)' is supported on 'windows'CA1416: 'Registry.CurrentUser' is supported on 'windows'CA1416: 'RegistryKey.GetValue(string?)' is supported on 'windows'
你有三个选择来处理这些警告 。
- 调用保护:在调用 API 之前 , 你可以使用 OperatingSystem.IsWindows() 来检查当前运行环境是否是 Windows 系统 。
- 将调用标记为 Windows 专用:在某些情况下 , 通过 [SupportedOSPlatform("windows")] 将调用成员标记为特定平台也有一定的意义 。
- 删除代码:一般来说 , 这不是你想要的 , 因为这意味着当你的代码被 Windows 用户使用时 , 你会失去保真度(fidelity) 。 但对于存在跨平台替代方案的情况 , 你应该尽可能使用跨平台方案 , 而不是平台特定的 API 。 例如 , 你可以使用一个 XML 配置文件来代替使用注册表 。
- 抑制警告:当然 , 你可以通过 .editorconfig 或 #pragma warning disable 来抑制警告 。 然而 , 当使用特定平台的 API 时 , 你应该更喜欢选项 (1) 和 (2) 。
private static string GetLoggingDirectory(){if (OperatingSystem.IsWindows()){using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Fabrikam")){if (key?.GetValue("LoggingDirectoryPath") is string configuredPath)return configuredPath;}}string exePath = Process.GetCurrentProcess().MainModule.FileName;string folder = Path.GetDirectoryName(exePath);return Path.Combine(folder, "Logging");}