自从.NET 5 发布以来,许多人都在问它对.NET Standard 意味着什么,想知道它是不是还能吸引主流开发人员的兴趣。
在本文中,我将介绍.NET 5 是如何改善代码共享并替代.NET Standard 的。同时,我还将介绍一些仍然需要.NET Standard 的情况。
.NET 5 将是一个具有一组统一特性和 API 的单一产品,可用于 windows 桌面应用、跨平台移动应用、控制台应用、云服务和网站。
为更好地反映这一点,我们更新了目标框架名称(TFM):
我们不会再发布.NET Standard 的新版本了,但是.NET 5 和所有将来的版本都会继续支持.NET Standard 2.1 和更早版本。你应该将net5.0(及以后的版本)视为未来共享代码的基础。
由于net5.0是所有这些新 TFM 的共享基础,因此这意味着运行时、库和新的语言特性将围绕这个版本号来运作。例如,为了使用 C# 9,你需要使用net5.0或net5.0-windows。
.NET 5 和将来所有的版本都会一直支持.NET Standard 2.1 和更早版本。从.NET Standard 目标切换到.NET 5 的唯一理由是可以访问更多的运行时特性、语言特性或 API。因此,你可以将.NET 5 视为.NET Standard 的 vNext。
新代码呢?你还是应该从.NET Standard 2.0 开始还是应该直接进入.NET 5?这要看具体情况。
那到底该怎么办?我的建议是,广泛使用的库最终将同时将.NET Standard 2.0 和.NET 5 作为目标:支持.NET Standard 2.0 可为你提供最大的支持范围,而支持.NET 5 可确保你能为客户提供.NET 5 上最新的平台特性。
在几年内,可重用库的选项仅涉及netX.Y的版本号,这基本上就是为.NET 构建库的标准途径——通常来说,你会希望支持某些较旧的版本,以确保获得最大的采用率。
总结一下:
.NET Standard 让开发人员可轻松创建适用于所有.NET 平台的库。但是,.NET Standard 仍然存在三个问题:
让我们看看.NET 5 是如何解决这三个问题的。
.NET Standard 是.NET 平台尚未在实现级别融合的时候设计的。这使开发人员很难编写需要在不同环境中运行的代码,因为不同的负载使用了不同的.NET 实现。
.NET Standard 的目标是统一基类库(BCL)的特性集,以便你可以编写可在任何地方运行的库。这对我们很有帮助:.NET Standard 在前 1000 个软件包中得到了 77%以上的支持。而且,如果我们查看 NuGet.org 上最近 6 个月中更新的所有软件包,则采用率是 58%。
支持.NET Standard 的包:
但是单独标准化 API 集会有代价。每当我们添加新的 API 时都需要协调。.NET 开源社区(包括.NET 团队)通过提供新的语言特性、可用性改进、新的跨领域特性(例如Span<T>)或支持新的数据格式或网络协议,以在 BCL 中不断创新前进。
尽管我们可以将新类型作为 NuGet 包来提供,但我们不能以这种方式在现有类型上提供新的 API。因此从一般意义上讲,BCL 中的创新需要交付新版本的.NET Standard。
在.NET Standard 2.0 之前,这并不是什么大问题,因为我们仅对现有的 API 进行了标准化。但是在.NET Standard 2.1 中,我们对全新的 API 进行了标准化,因此我们遇到了很多麻烦。
这种摩擦从何而来?
.NET Standard 是所有.NET 实现都必须支持的 API 集,因此它有一个编辑准则,即所有 API 必须由.NET Standard 审查委员会审查。该委员会由.NET 平台实现者和.NET 社区的代表组成,目标是仅标准化我们可以在所有当前和将来的.NET 平台中真正实现的 API。这些检查是必要的,因为.NET 堆栈的实现方式不同,约束条件也不同。
我们预测到了这种摩擦,这就是为什么我们之前提到.NET Standard 只会标准化至少在一个.NET 实现中提供 API 的原因。一开始这似乎挺合理,但随后,你就意识到.NET Standard 不能频繁发布新版本。因此,如果某个特性错过了某个特定版本,则可能需要等上几年才能发布,甚至可能需要更长时间才能等到这个版本的.NET Standard 得到广泛支持。
对某些特性来说,我们感到机会成本太高了,因此我们采取了非常规的行动来标准化尚未交付的 API(例如IAsyncEnumerable<T>)。但是,对所有特性都这么做,实在是太昂贵了,这就是为什么许多特性仍然错过了.NET Standard 2.1 的原因(例如新的硬件内部函数)。
但是,如果只有一个代码库呢?如果这个代码库需要支持现在让.NET 实现分裂的所有选项,例如同时支持即时(JIT)编译和提前(AOT)编译,又会怎么样?
从一开始,我们就将所有这些选项都纳入了特性设计,而不是事后才考虑。在这种情况下,标准化的 API 集在构造上就是通用的 API 集。一个特性被实现后,由于代码库是共享的,因此这个特性立刻就能被所有人使用。
将 API 集与对应的实现分离,不仅仅是减缓了 API 的可用性,这也意味着我们需要将.NET Standard 版本映射到它们的实现上。随着时间的推移,我需要向越来越多的人解释这种关系,于是发现这种看似简单的想法其实非常复杂。我们尽了最大的努力来简化它,但毕竟这是固有的复杂性,因为 API 集和实现是分开独立提供的。
我们统一了 .NET 平台,在它们下面又增加了一个合成平台,代表了通用的 API 集。这幅受 XKCD 启发的漫画就画得很明白:
.NET 5 要做的就是真正在框架中合并一些部分,从而解决这个问题:.NET 5 提供了一个统一的实现,所有部分都在相同的基础上构建,并因此获得相同的 API 形状和版本号。
在设计.NET Standard 时,我们必须做出务实的妥协,以免对库生态系统造成过大的破坏。换句话说,我们必须包括一些只适用于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。
创建以 net5.0 为目标的项目时,可以引用 Microsoft.Win32.Registry 包。但当你开始使用它时,会收到以下警告:
private static string GetLoggingDirectory()
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"SoftwareFabrikam"))
{
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'
关于如何解决这些警告,你有三个选项:
为了守护调用,请在 System.OperatingSystem 类上使用新的静态方法,例如:
private static string GetLoggingDirectory()
{
if (OperatingSystem.IsWindows())
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"SoftwareFabrikam"))
{
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");
}
要将代码标记为 Windows 专属,请应用新的SupportedOSPlatform属性:
[SupportedOSPlatform("windows")]
private static string GetLoggingDirectory()
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"SoftwareFabrikam"))
{
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");
}
在这两种情况下,使用注册表的警告都会消失。
关键区别在于,在第二个示例中,分析器现在将针对GetLoggingDirectory()的调用站点发出警告,因为它现在被视为 Windows 特定的 API。换句话说,你将进行平台检查的要求转发给了调用者。
[SupportedOSPlatform]属性可以应用于成员、类型或程序集级别。BCL 本身也使用此属性。例如,程序集Microsoft.Win32.Registry应用了此属性,所以分析器能先知道注册表是 Windows 特定的 API。
请注意,如果你以net5.0-windows为目标,此属性将自动应用于你的程序集。这意味着从net5.0-windows使用 Windows 专属的 API 永远不会产生任何警告,因为你的整个程序集都被视为 Windows 专属的。
Blazor WebAssembly 项目在浏览器沙箱中运行,这限制了你可以使用的 API。例如,尽管线程和进程创建都是跨平台的 API,但我们无法使这些 API 在 Blazor WebAssembly 中运行,这意味着它们会抛出PlatformNotSupportedException警告。我们已经用[UnsupportedOSPlatform("browser")]标记了这些 API。
假设,你将GetLoggingDirectory()方法复制并粘贴到一个 Blazor WebAssembly 应用程序中。
private static string GetLoggingDirectory()
{
//...
string exePath = Process.GetCurrentProcess().MainModule.FileName;
string folder = Path.GetDirectoryName(exePath);
return Path.Combine(folder, "Logging");
}
你会收到以下警告:
CA1416 'Process.GetCurrentProcess()' is unsupported on 'browser'
CA1416 'Process.MainModule' is unsupported on 'browser'
为了处理这些警告,你的选项和处理 Windows 特定的 API 时基本相同。
你可以守护调用:
private static string GetLoggingDirectory()
{
//...
if (!OperatingSystem.IsBrowser())
{
string exePath = Process.GetCurrentProcess().MainModule.FileName;
string folder = Path.GetDirectoryName(exePath);
return Path.Combine(folder, "Logging");
}
else
{
return string.Empty;
}
}
或者,你可以将成员标记为 Blazor WebAssembly 不支持的成员:
[UnsupportedOSPlatform("browser")]
private static string GetLoggingDirectory()
{
//...
string exePath = Process.GetCurrentProcess().MainModule.FileName;
string folder = Path.GetDirectoryName(exePath);
return Path.Combine(folder, "Logging");
}
由于浏览器沙箱的限制非常严格,因此不能预期所有类库和 NuGet 包都能在 Blazor WebAssembly 中工作。此外,绝大多数库也不一定能在 Blazor WebAssembly 中运行。
因此,目标为net5.0的常规类库不会看到 Blazor WebAssembly 不支持的 API 警告。你必须通过在项目文件中添加项,来显式声明你打算在 Blazor WebAssembly 中支持你的项目:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
</Project>
如果你要构建一个 Blazor WebAssembly 应用程序,则不必这样做,因为Microsoft.NET.Sdk.BlazorWebAssembly SDK 会自动执行此操作。
.NET 5 及其后续版本将是一个单一代码库,它支持桌面应用、移动应用、云服务、网站以及未来.NET 将会运行的任何环境。
你可能会认为“等等,这听起来不错,但是如果有人想创建一个全新的实现该怎么办”。这也是可以的。但实际上没有人会从头开始。这样的实现很可能是当前代码库( dotnet/runtime )的分支。例如,Tizen(三星用于智能设备的平台)使用了一个经过少量改动的.NET Core,并且在顶层使用了一个三星定制的应用模型。
分叉保留了合并关系,维护人员可以从不受其更改影响的 BCL 创新中受益,从而继续从 dotnet/runtime 仓库中拉取新更改。这与 linux 发行版的机制非常相似。
当然,在某些情况下,你可能想创建一种非常不同的.NET“类别”,例如在没有当前 BCL 的情况下最小化运行时。但这意味着它无论如何都无法利用现有的.NET 库生态系统,也就是说,它也不会实现.NET Standard。我们对这个方向总体来说并不感兴趣,但是.NET Standard 和.NET Core 的融合并不能阻止这一点,也不会为其增加障碍。
作为库作者,你可能想知道.NET 5 何时才能得到广泛支持。展望未来,我们将在每年 11 月发布.NET,每隔一年将发布一个长期支持(LTS)版本。
.NET 5 将于 2020 年 11 月发布,.NET 6 将于 2021 年 11 月发布,是 LTS 版。我们定下了这个固定的时间表,以便大家更轻松地安排更新计划(如果你是应用开发人员),和预测对.NET 版本的支持需求(如果你是库开发人员)。
由于.NET Core 能够并行安装,因此新版本的采用速度相当快,其中 LTS 版本最为流行。实际上,.NET Core3.1 是有史以来普及最快的.NET 版本。
.NET 5 时间表
可以预计每次交付时,我们都会一并更新所有框架名称。例如,计划可能是这个样子:
换句话说,你基本上可以指望:无论我们在 BCL 中所做的创新如何,无论它们运行在哪个平台上,你都将能在所有应用模型中使用它。这也意味着,只要运行最新版本的.NET,就始终可以在所有应用模型中使用为最新的net框架提供的库。
这种模式消除了.NET Standard 版本控制的复杂性,因为每次发布新版时,你都可以假定所有平台都将立即且完全支持新版本。我们还使用前缀命名约定来巩固这一承诺。
.NET 的新版本可能会增加对其他平台的支持。例如,我们将为.NET 6 添加对 Android 和 iOS 的支持。相反,我们可能会停止支持不再流行的平台。.NET 6 中就没有net5.0-someoldos目标框架。我们没有移除某个平台的计划,但是这种模式下存在这种可能性。这会是一件大事,提前无法预料,而且会提前很长时间宣布。这与.NET Standard 使用的模式相同,例如,Windows Phone 已经无法运行更高版本的.NET Standard 了。
我们最初考虑为 WebAssembly 添加 TFM,例如net5.0-wasm。我们出于以下原因决定不这样做:
如上所述,我们标记了浏览器沙箱中不支持的 API,例如System.Diagnostics.Process。如果你是从浏览器应用中使用这些 API,则会收到警告,告知你不支持该 API。
net5.0适用于在各种平台上运行的代码。它合并并替换了netcoreapp和netstandard名称。我们还有特定于平台的框架,例如net5.0-windows(以及后来的net6.0-android和net6.0-ios)。
由于标准与其实现之间没有区别,因此你可以比.NET Standard 更快地利用新特性。而且,由于采用了命名约定,你就能轻松判断谁可以使用给定的库,而无需查阅.NET Standard 版本表。
.NET Standard 2.1 将是.NET Standard 的最后版本,而.NET 5 和所有将来的版本将继续支持.NET Standard 2.1 和更早版本。你应该将 net5.0(及以后的版本)视为共享代码的基础。
原文链接:
https://devblogs.microsoft.com/dotnet/the-future-of-net-standard/