重新统一的 .NET平台-.NET 5
当 Microsoft 在 2019 年 5 月的 Microsoft Build 2019 大会上宣布推出 .NET 5 时,它标志着跨桌面、Web、移动、云和设备平台工作的开发人员向前迈出了重要一步。事实上,.Net 5 是一个罕见的平台更新,它统一了不同的框架、减少了代码复杂性,并显著提高了跨平台的可实现性。
这不是一件容易的事情。Microsoft 提议合并几个关键框架的源代码流 - .NET Framework、.NET Core 和 Xamarin/Mono。这项工作甚至将统一本世纪初分离的线程,并为开发人员的工作提供一个目标框架。
图 1中的源代码流概念显示了每个框架的时间轴是如何同步的,并最终在 2020 年 11 月合并为 .NET 5 这样的单一线程。(注意,.NET Framework 在图中缩写为 .Net FW。) 发布时,.NET 5 将覆盖 .NET Framework 4.8、Mono 5.0 和 .NET Core 3.0。
图 1 从 .NET、Mono 和共享源计划到 .NET 5 的源代码流概念(单击可查看较大版本)
不可否认,图 1更侧重于概念而非现实,包含源代码分叉(更有可能只是复制)而非分支,且功能(如 Windows Presentation Foundation [WPF] 或 Windows 窗体)是迁移而不是合并。尽管如此,此信息图提供了一个相当透明的 .NET 源代码的原始历史视图,展示了它从三个主要分支一直发展到 .NET 5的过程。
这项工作的结果是一个统一的平台,在所有平台(桌面、Web、云、移动等)上执行 .NET 5 框架。图 2描述了此统一的体系结构。
图 2 .NET 5 — 一个统一的平台
源情景
奇妙的是,不同的 .NET 框架 - 例如 Microsoft 的共享源公共计划 (Rotor)、SilverLight、Windows Phone、.NET Core 和 .NET Framework(但不是 Mono) - 最初是从相同的源代码编译而来。换句话说,所有源代码都保存在单个存储库中,并由所有 .NET 框架共享。通过这样做,Microsoft 能够确保不同框架共享的 API 来自相同源代码,并且具有相同的签名。唯一区别是哪些 API 是共享的。(注意 .NET “framework,”小写和 .NET “Framework,” 大写的用法,前者泛指所有 .NET 框架,后者指的是 Windows .NET Framework,如 .NET Framework 4.8。)
为了实现针对不同框架的单一源代码,使用了各种子集技术,比如大量的 #ifdefs。同样令人感兴趣的是,如何将一组正交的子集技术(即不同的 #ifdefs)集成到 .Net 源代码中,以生成 Rotor 的跨平台支持,从而支持从相同的代码库快速开发 Silverlight 和(更高版本).NET Core。
虽然保留了一组正交子集技术,但支持跨平台编译的技术集(生成不同框架的子集)正在被删除。(例如,请查看 bit.ly/2WdSzv2 中的拉取请求,它删除了几个过时的 #ifdefs。) 之所以现在可以将其删除,是因为在 .NET 1.1 发布后的几天内,.NET Core 和 .NET Framework 源代码就被分叉了(更像是复制)。这实际上解释了为什么会有两个独立的 .NET 源代码网站:.NET Framework 位于referencesource.microsoft.com,.NET Core 位于source.dot.net。这是两个独立的代码库。
创建分支而不是继续使用子集的决定反映了维护向后兼容性(.NET Framework 的高优先级)和创新(.NET Core 的优先级)之间的紧张关系。在很多情况下,维护兼容性与纠正或改进 .NET API 存在冲突,因此如果要同时实现这两个目标,就必须分离源代码。事实上,当 API 不同且版本不兼容时,使用 #ifdefs 不再是分离框架的有效方法。
然而,随着时间的推移,另一个冲突出现了,即允许开发人员创建能够在两个框架中成功执行的库。为了实现这一点,需要一个 API 标准来向开发人员保证,一个框架有一组由该标准标识的特定 API。这样,如果他们只利用标准中的 API,他们的库将是跨框架兼容的(完全相同的程序集可以在不同框架上运行,甚至不需要重新编译)。
随着向 .NET 添加每个新功能(例如 Span<T>),维护与旧版本框架的向后兼容性变得愈加困难。具体而言,挑战在于在 .NET Framework 中支持新 .NET Standard 版本中的概念(实际上是新的 .NET Core 创新)。此外,尽管不那么重要,但每个新版本的 .NET Standard 都包含越来越多的 API 集,直到成为维护 .NET Framework 和 .NET Core 之间的 .NET Standard 兼容性的负担。
精英荟萃,团结协作
由于采用了标准,这两个框架开始变得越来越相似。随着 API 变得更加一致,开始出现一个明显的问题:为什么不把单独的代码库移回去呢?事实上,从 .NET Core 3.0 预览版开始,很多 .NET Framework WPF 和 Windows API 都被挑拣并合并到 .NET Core 3.0 代码库,这就是实际情况。.NET Core 3.0 源代码成为一体,.NET Framework 4.8 中的现代化功能(桌面、云、移动和 IoT)也是如此。
目前还有一个主要的 .NET 框架还没有介绍:Mono/Xamarin。虽然 Rotor 源代码已公开发布,但使用它将违反许可协议。相反,Mono 最初是一个单独的绿色领域开发项目,其目标是创建一个与 Linux 兼容的 .NET 版本。Mono 框架随着时间的推移不断发展,直到 Novell 在 2003 年收购了该公司 (Ximian),然后在 8 年后 Novell 将其出售给 Attachmate 后关闭。2011 年 5 月,Ximian 管理迅速进行了改革,更名为 Xamarin。不到两年时间,Xamarin 开发了一个跨平台的 UI 代码库,该代码库同时在 Android 和 iOS 系统上运行,并在后台利用目前 Mono 的封闭源代码跨平台版本。
2016 年,Microsoft 收购了 Xamarin,将所有 .NET 框架源代码置于一家公司的控制之下。不久之后,Mono 和 Xamarin SDK 作为开放源代码发布。
这就是 2019 年上半年的情形,实质上推进了两个主要代码库:.NET Core 3.0 和 Mono/Xamarain。(虽然任何人都能预测到 Microsoft 将在 Windows 上支持 .NET Framework 4.8,但随着新应用程序战略平台的发展,.NET Core 3.0 及更高版本 .NET 5 将覆盖它。) 与此同时,还有 .NET Standard 和即将发布的 .NET Standard 2.1 中 API 的统一。
同样,随着 API 越来越接近,问题又出现了,我们不能将 .NET Core 3.0 与 Mono 合并吗?事实上,这项工作已经开始。Mono 目前已经有三分之一的 Mono 源代码、三分之一的 CoreFx 和三分之一的 .NET Framework 引用源。这为 .NET 5 在 Microsoft Build 2019 大会上发布做好了准备。
.NET 5 的优势
此统一版本的 .NET 5 将支持所有 .NET 应用程序类型:Xamarin、ASP.NET、IoT 和桌面。此外,它将利用一个单独的 CoreFX/基类库 (BCL)、两个独立的运行时和运行时代码库(因为很难将两个截然不同的运行时单独作为源)和一个工具链(比如 dotnet CLI)。结果将是行为、API 和开发人员体验之间的一致性。例如,在每个不同平台上将运行一组库,而不是三个 System.* API 实现。
.NET 的统一有很多优点。将框架、运行时和开发人员工具集统一到一个代码库中,将减少开发人员(Microsoft 和社区)需要维护和扩展的重复代码量。此外,正如我们最近对 Microsoft 的期许,所有 .NET 5 源代码都将是开放源代码。
合并后,所有平台都可以使用每个单独框架独有的许多功能。例如,这些平台的 csproj 类型将统一为深受欢迎的、简单的 .NET Core csproj 文件格式。因此,.NET Framework 项目类型将能够利用 .NET Core csproj 文件格式。虽然 Xamarin 和 .NET Framework(包括 WPF 和 Windows 窗体)csproj 文件需要转换为 .NET Core csproj 文件格式,但该任务类似于从 ASP.NET 转换为 ASP.NET Core。幸运的是,得益于诸如 ConvertProjectToNETCore3 之类的工具,现在实现起来更加容易(请参阅 bit.ly/2W5Lk3D)。
另一个显著差异是 Xamarin 和 .NET Core/.NET Framework 的运行时行为。前者使用静态编译模型,使用提前 (AOT) 编译将源代码编译为平台的本机源代码。而 .NET Core 和 .NET Framework 使用即时 (JIT) 编译。幸运的是,在 .NET 5 中,这两种模型都将受支持,具体取决于项目类型目标。
例如,可以选择将 .NET 5 项目编译为单个可执行文件,该文件将在运行时使用 JIT 编译器 (jitter),或使用本机编译器在 iOS 或 Android 平台上工作。大多数项目都会利用 jitter,但对于 iOS 来说,所有代码都是 AOT。对于客户端 Blazor,运行时是 Web 程序集 (WASM),Microsoft 打算 AOT 编译少量托管代码(大约 100 kb 到 300 kb),而其余代码将被解释。(AOT 代码很大,因此网络成本是一个相当大的负担。)
在 .NET Core 3.0 中,可以编译到单个可执行文件,但该可执行文件实际上是运行时所需执行的所有文件的压缩版本。在执行该文件时,它首先将自己展开到一个临时目录中,然后从包含所有文件的目录中执行应用程序的入口点。相反,.NET 5 将创建一个实实在在的、可直接就地执行的单个可执行文件。
.NET 5 的另一个显著特性是与 Java 和 Objective-C(包括 Swift)中源代码的互操作性。自早期版本以来,这一直是 Xamarin 的一个特性,但将扩展到所有 .NET 5 项目。例如,你将能够在 csproj 文件中包含 jar 文件,并且能够直接从 .NET 代码调用 Java 或 Objective-C 代码。(遗憾的是,对 Objective-C 的支持可能会比 Java 晚。) 需要注意的是,.NET 5 和 Java/Objective-C 之间的互操作性只针对进程内通信。与同一台计算机上的其他进程甚至不同计算机上的进程的分布式通信可能需要序列化为基于 REST- 或 RPC- 的分布式调用。
.NET 5 中不包含的内容
尽管 .NET 5 框架中提供了一组重要 API,但它并不包括过去 20 年左右开发的所有 API。可以合理地预期 .NET Standard 2.1 中标识的所有 API 都将受到支持,但一些更“旧”的 API(包括 Web Forms、Windows Communication Foundation (WCF) 服务器和 Windows 工作流)将不受支持。它们注定只保留在 .NET Framework 中。如果希望在 .NET 5 中实现相同的功能,请考虑移植以下 API:
- ASP.NET Web Forms => ASP.NET Blazor
- WCF 服务器和远程处理 => gRPC
- Windows Workflow (WF) => Core WF (github.com/UiPath/corewf)
缺少 WCF 服务器支持无疑会让一些人失望。然而,Microsoft 最近决定在 MIT 开放源码许可证下发布软件,其命运掌握在社区手中(请参阅 github.com/CoreWCF/CoreWCF)。要独立于 .NET Framework 发布还有大量的工作要做,但与此同时,客户端 WCF API 可用(请参阅 github.com/dotnet/wcf)。
意图声明
就在 Microsoft 计划将其开发人员框架统一到 .NET 5 下时,该公司已经宣布将为其统一的 .NET 发布采用常规节奏(请参见图 3)。展望未来,预计 .NET 正式版将在每年的第 4 个季度发布。在这些版本中,每第二个版本都将是长期支持 (LTS) 版本,为此,Microsoft 将在后续 LTS 版本发布后提供至少三年或一年的支持,以较长者为准。换句话说,你始终有至少三年的时间将应用程序升级到下一个 LTS 版本。有关 .NET Core 支持策略的详细信息,以及可以合理预期成为 .NET 5 及更高版本支持策略的内容,请参阅 bit.ly/2Kfkkw0。
图 3 .NET 发布计划
在此情况下,.NET 5 仍然只是一个公告 — 如果你愿意,也可以说是意图声明。有很多工作要完成。即便如此,此公告仍令人瞩目。当 .NET Core 首次发布时,旨在提供一个跨平台的 .NET 版本,该版本可突出显示 Azure(特别是 Azure 的平台即服务 [PaaS] 部分,以及 Linux 和 Linux 容器中对 .NET 的支持)。
在最初的概念中,认为所有 .NET Framework 都可以移植到 .NET Core 的想法是不现实的。在 .NET Core 2.0 发布前后,这种情况开始发生变化。Microsoft 意识到,它需要为所有 .NET 框架版本定义框架标准,使在一个框架上运行的代码能够移植到另一个框架上。
当然,此标准后来被称为“.NET Standard”。其目的在于确定框架需要支持的 API,以便针对标准的库可以依赖于一组可用的特定 API。事实证明,定义标准然后使用 Xamarin/Mono、.NET Core 和 .NET Framework 实现它,成为使 .NET 5 统一策略成为可能的关键组件。
例如,一旦每个框架都实现了支持 .NET Standard API 集的代码,那么将单独的代码库合并为一个代码库(某种程度上的重构)似乎是合乎逻辑的。而且,如果行为不同(例如,JIT 与 AOT 编译),为什么不合并代码,以便所有平台都支持方法和功能呢?这项工作并不简单,但其结果是在降低复杂性和维护方面向前迈出了一大步,同时将所有平台功能都统一起来。
也许令人惊讶的是,使统一成为可能的 .NET Standard 很可能会使 .NET Standard 变得无关紧要。事实上,随着 .NET 5 的出现,我们怀疑是否会有另一个版本的 .NET Standard:.NET 5 和之后的每个版本都将是标准版本。
总结
正如人们所说,时机决定一切,对于 .NET 5 也是如此。实际上,在开始开发 .NET Core 时,对 .NET Framework 的全面重写甚至是不可想象的。当时,Microsoft 正在响应在 Linux、容器中和 PaaS 上显著增强 Azure 托管体验的需求。因此,公司专注于推出一些产品来满足客户和 Azure 产品团队的需求。
在 .NET Core 2.0 中,任务扩展到匹配 .NET Framework 中发现的功能。同样,团队专注于发布一些可行的产品,而不是盲目地推出过多产品。但随着 .NET Core 3.0 的发布和 .NET Standard 2.1 的实现,情况开始发生变化。当一个新功能或 bug 出现时,必须对三个不同框架进行更改,这种想法令人恼火,还将产生费用。和任何优秀的开发人员一样,很快就萌生了将代码尽可能多地重构为单个代码库的想法。
因此,.NET 5 诞生了。统一每个框架所有功能的想法也随之诞生 - 无论是简单的 csproj 格式、采用开放源代码开发模型、支持与 Java 和 Objective-C(包括 Swift)的互操作性,还是支持 JIT 和 AOT 编译。就这样,下一步显然是一个单一、统一的框架的构想,希望 Microsoft 内外各人士都会为此感到欣慰。