.NET Standard - 论.NET Core 和 .NET Standard关系
作为 .NET 系列的最新成员,.NET Core 和 .NET Standard 的概念及其与 .NET Framework 的区别并不十分明确。在本文中,我将准确介绍每个产品及其适用场景。
在详细介绍之前,建议先审视一下 .NET 的总体情况,了解 .NET Core 和 .NET Standard 是如何在这一体系中发挥作用的。.NET Framework 是在 15 年前首次推出,当时只有一个 .NET 堆栈可用于生成 Windows 桌面和 Web 应用程序。从那以后,出现了其他 .NET 实现。例如,可用于生成 iOS/Android 移动应用和 macOS 桌面应用程序的 Xamarin,如图 1**** 所示。
图 1:.NET 总体情况
下面说明了 .NET Core 和 .NET Standard 是如何在这一体系中发挥作用的:
- .NET Core:这是最新的 .NET 实现。它不仅开放源代码,而且还适用于多个 OS。使用 .NET Core,可以生成跨平台的控制台应用程序、ASP.NET Core Web 应用程序和云服务。
- .NET Standard:这是所有 .NET 实现都必须实现的一组基本 API,通常称为“基类库 (BCL)”。通过定目标到 .NET Standard,可以生成能跨所有 .NET 应用程序共享的库,无论它们是在哪个 .NET 实现或 OS 上运行。
.NET Core 简介
作为 .NET Framework 和 Silverlight 分支,.NET Core 是全新的 .NET 实现,不仅跨平台,而且还完全开放源代码。通过启用独立式 XCOPY 部署,它已经过优化,更适用于移动和服务器工作负载。
为了能够更好地认识 .NET Core,接下来将深入了解如何进行 .NET Core 开发。为此,将同时探索新的命令行工具。也可以使用 Visual Studio 2017 进行 .NET Core 开发,但本文读者很可能已相当熟悉 Visual Studio,因此我将重点介绍新体验。
在 .NET 创建之初,它经过了大量优化,以便用户能够在 Windows 上快速开发应用程序。实际上,这意味着 .NET 开发与 Visual Studio 是不可分开的。可以肯定的是:使用 Visual Studio 进行开发是一种奇妙的感受。这样开发的工作效率超高,调试程序是我使用过的最好用的。
不过,有时,使用 Visual Studio 并不是最便捷的选择。假设只是想通过 .NET 学习 C#。在这种情况下,就无需下载并安装好几个 GB 的 IDE。或者,假设要通过 SSH 访问 Linux 计算机。在这种情况下,使用 IDE 根本就行不通。又或者,假设只是喜欢使用命令行接口 (CLI)。
正因为此,名为 .NET Core CLI 的一流 CLI 诞生了。.NET Core CLI 的主驱动程序称为“dotnet”。 它可用于开发的几乎所有方面,包括创建、生成、测试和打包项目。接下来,将了解具体操作。
首先,创建并运行 Hello World 控制台应用程序(我使用的是 Windows PowerShell,而在 macOS 或 Linux 上使用 Bash 也同样有效):
C#复制
$ dotnet new console -o hello$ cd hello$ dotnet runHello World!
CLI 中的“dotnet new”命令相当于 Visual Studio 中的“文件 | 新建项目”。可以创建各种不同类型的项目。键入“dotnet new”可以查看预安装的各种模板。
现在,将一些逻辑提取到类库中。为此,请先创建与“hello”项目平行的类库项目:
C#复制
$ cd ..$ dotnet new library -o logic$ cd logic
由于要封装的逻辑是构造 Hello World 消息,因此将 Class1.cs 的内容更改为以下代码:
C#复制
namespace logic{ public static class HelloWorld { public static string GetMessage(string name) => $"Hello {name}!"; }}
此时,还应将 Class1.cs 重命名为 HelloWorld.cs:
C#复制
$ mv Class1.cs HelloWorld.cs
请注意,无需因为有此更改而更新项目文件。.NET Core 中使用的新项目文件只包括项目目录中的所有源文件。因此,添加、删除和重命名文件不再需要修改项目。这样一来,可以更顺畅地使用命令行执行文件操作。
若要使用 Hello World 类,需要将 hello 应用程序更新为引用逻辑库。为此,可以编辑项目文件,也可以使用 dotnet add reference 命令:
C#复制
$ cd ../hello$ dotnet add reference ../logic/logic.csproj
现在,将 Program.cs 文件更新为使用 HelloWorld 类,如图 2 所示。
图 2:将 Program.cs 文件更新为使用 HelloWorld 类
C#复制
using System;using logic;namespace hello{class Program{static void Main(string[] args){Console.Write("What's your name: ");var name = Console.ReadLine();var message = HelloWorld.GetMessage(name);Console.WriteLine(message);}}}
若要生成并运行应用程序,只需键入 dotnet run:
C#复制
$ dotnet runWhat's your name: ImmoHello Immo!
还可以通过命令行创建测试。CLI 支持 MSTest 和常用的 xUnit 框架。此示例使用 xUnit:
C#复制
$ cd ..$ dotnet new xunit -o tests$ cd tests$ dotnet add reference ../logic/logic.csproj
更改 UnitTest1.cs 内容以添加测试,如图 3**** 所示。
图 3:更改 UnitTest1.cs 内容以添加测试
C#复制
using System;using Xunit;using logic;namespace tests{public class UnitTest1{[Fact]public void Test1(){var expectedMessage = "Hello Immo!";var actualMessage = HelloWorld.GetMessage("Immo");Assert.Equal(expectedMessage, actualMessage);}}}
现在,可以调用 dotnet test 运行测试:
C#复制
$ dotnet testTotal tests: 1. Passed: 1. Failed: 0. Skipped: 0.Test Run Successful.
为了让操作变得更有趣一点,将创建简单的 ASP.NET Core 网站:
C#复制
$ cd ..$ dotnet new web -o web$ cd web$ dotnet add reference ../logic/logic.csproj
编辑 Startup.cs 文件,并将 app.Run 调用更改为使用 HelloWorld 类,如下所示:
C#复制
app.Run(async (context) =>{ var name = Environment.UserName; var message = logic.HelloWorld.GetMessage(name); await context.Response.WriteAsync(message);});
若要启动开发 Web 服务器,只需再次运行 dotnet run:
C#复制
$ dotnet runHosting environment: ProductionNow listening on: http://localhost:5000Application started. Press Ctrl+C to shut down.
转到所显示的 URL(应为 http://localhost:5000)。
此时,项目结构应如图 4 所示。
图 4:创建的项目结构
C#复制
$ tree /f│├───hello│ hello.csproj│ Program.cs│├───logic│ HelloWorld.cs│ logic.csproj│├───tests│ tests.csproj│ UnitTest1.cs│└───webProgram.csStartup.csweb.csproj
为了能够更方便地使用 Visual Studio 编辑文件,还将创建解决方案文件,并将所有项目添加到解决方案中:
C#复制
$ cd ..$ dotnet new sln -n HelloWorld$ ls -fi *.csproj -rec | % { dotnet sln add $_.FullName }
可以看到,.NET Core CLI 功能非常强大,能够为具有其他背景的开发者提供相当熟悉的精益体验。虽然此过程是将 dotnet 与 Windows PowerShell 结合使用,但如果是在 Linux 或 macOS 上,使用体验也会非常相似。
.NET Core 的另一巨大优势在于,它支持独立式部署。可以使用 Docker 对应用程序进行容器化处理,以便它能够有自己的 .NET Core 运行时副本。这样一来,可以在使用不同版本 .NET Core 的同一台计算机上运行各种应用程序,而它们则互不干扰。由于 .NET Core 开放源代码,因此还可以添加每日版或甚至自己修改或生成的版本,其中可能包括自己所做的修改。不过,这并不在本文的讨论范围之内。
.NET Standard 简介
在开发者生成新式体验时,应用程序通常跨越多种外形规格,因而也就跨越多个 .NET 实现。今时今日,客户非常希望可以在手机上使用 Web 应用程序,并希望能够通过基于云的后端共享数据。使用笔记本电脑时,他们也希望可以通过网站获得访问权限。对于自己的基础结构,很可能希望可以借助命令行工具和潜在的桌面应用程序,允许员工管理系统。请参阅图 5,了解具有此类用途的不同 .NET 实现。
图 5:介绍了 .NET 实现
OS | 开源 | 用途 | |
.NET Framework | Windows | 不能 | 用于生成在 IIS 上运行的 Windows 桌面应用程序和 ASP.NET Web 应用程序。 |
.NET Core | Windows、Linux、macOS | 是的 | 用于生成跨平台的控制台应用程序、ASP.NET Core Web 应用程序和云服务。 |
Xamarin | iOS、Android、macOS | 是的 | 用于生成适用于 iOS 和 Android 的移动应用,以及适用于 macOS 的桌面应用程序。 |
.NET Standard | 无 | 是的 | 用于生成可以从所有 .NET 实现(如 .NET Framework、.NET Core 和 Xamarin)引用的库。 |
在这样的环境下,代码共享成为重大挑战。需要了解 API 的可用位置,并确保共享组件仅使用跨所有要用 .NET 实现都支持的 API。
此时,.NET Standard 就派上用场了。.Net Standard 是一种规范。每个 .NET Standard 版本都定义了一组 API。为了与相应版本保持一致,所有 .NET 实现都必须提供这些 API。可以将 .NET Standard 看作是另一个 .NET 堆栈,不同之处在于无法为其生成应用程序,只能生成库。若希望可以从任意位置引用库,应对库使用此 .NET 实现。
大家不禁想知道,.NET Standard 涵盖了哪些 API。如果熟悉 .NET Framework,就应该熟悉我之前提到的 BCL。BCL 是与 UI 框架和应用程序模型无关的一组基础 API。它包括基元类型、文件 I/O、网络、反射、序列化、XML 等。
所有 .NET 堆栈都会实现某版 .NET Standard。经验法则是,生成新版 .NET 实现时,通常都会实现最新版 .NET Standard。
很贴切的例子是 HTML 和浏览器:将 HTML 规范看作是 .NET Standard,将不同的浏览器看作是 .NET 实现,如 .NET Framework、.NET Core 和 Xamarin。
此时,大家可能非常好奇,如何使用 .NET Standard。实际上,大家已经知道了。还记得之前创建逻辑类库时的情形吗? 接下来,将深入了解一下项目文件:
C#复制
$ cd logic$ cat logic.csproj<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup></Project>
将此文件与“hello”控制台应用程序项目文件进行对比:
C#复制
$ cd ..hello$ cat hello.csproj<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <ProjectReference Include="..logiclogic.csproj" /> </ItemGroup> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp2.0</TargetFramework> </PropertyGroup></Project>
可以看到,逻辑库的 TargetFramework 值为 netstandard2.0,而控制台应用程序的值为 netcoreapp2.0。TargetFramework 属性指明要定目标到的 .NET 实现。因此,控制台应用程序定目标到 .NET Core 2.0,而库定目标到 .NET Standard 2.0。也就是说,不仅可以从 .NET Core 应用程序引用逻辑库,还可以从生成的 .NET Framework 或 Xamarin 应用程序引用逻辑库。
遗憾的是,目前可用的大多数库都尚未定目标到 .NET Standard。大多数库都定目标到 .NET Framework。当然,并不是所有库都可以(或甚至应该)定目标到 .NET Standard。例如,包含 Windows Presentation Foundation (WPF) 控件的库需要定目标到 .NET Framework,因为 UI 并不是标准的一部分。不过,许多常规用途库定目标到 .NET Framework 只是因为,在它们创建时还没有 .NET Standard。
在 .NET Standard 2.0 推出后,API 集变得非常大,大多数(如果不是全部的话)常规用途库可以定目标到 .NET Standard。因此,目前 NuGet 上的全部库中有 70% 只使用现在属于 .NET Standard 的 API。尽管如此,其中只有一小部分明确标注为与 .NET Standard 兼容。
为了让开发者无障碍地使用它们,添加了一种兼容性模式。如果安装的 NuGet 包没有为目标框架提供库,也没有为 .NET Standard 提供库,那么 NuGet 会转而求助于 .NET Framework。也就是说,可以引用 .NET Framework 库,就像是定目标到 .NET Standard 一样。
我将演示一下具体操作。在此示例中,我将使用名为 PowerCollections 的常用集合库,它是在 2007 年编写的。它有一段时间没有更新了,仍定目标到 .NET Framework 2.0。我将从 NuGet 将此库安装到 hello 应用程序中:
C#复制
$ dotnet add package Huitian.PowerCollections
此库提供了 BCL 没有的其他集合类型,如不保证排序的包。接下来,将把 hello 应用程序更改为使用此库,如图 6 所示。
图 6:使用 PowerCollections 的示例应用程序
C#复制
using System;using Wintellect.PowerCollections;namespace hello{class Program{static void Main(string[] args){var data = new Bag<int>() { 1, 2, 3 };foreach (var element in data)Console.WriteLine(element);}}}
如果运行程序,将会看到以下警告:
C#复制
$ dotnet runhello.csproj : warning NU1701: Package 'Huitian.PowerCollections 1.0.0' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v2.0'. This may cause compatibility problems.132
那么,究竟发生了什么? hello 应用程序定目标到 .NET Core 2.0。由于 .NET Core 2.0 实现的是 .NET Standard 2.0,因此还有可引用 .NET Framework 库的兼容性模式。不过,并不是所有 .NET Framework 库都适用于各种 .NET 实现。例如,它们可能会使用 Windows 窗体或 WPF API。由于无法确定具体情况,因此 NuGet 会显示警告消息,提醒注意这种情况,这样就不会浪费时间排查可能由此导致的问题了。
请注意,每次生成时都会看到此警告。这样可避免在包安装期间根本看不到警告或忘记这种情况的问题。
当然,没有什么比每次生成时都需要忽略无法作为行动依据的警告更糟糕的了。因此,建议在验证应用程序后,为相应包禁用警告。由于应用程序运行正常(正确输出了所创建的包的内容),因此现在可以取消警告。为此,请编辑 hello.csproj 文件,并将 NoWarn 属性添加到包引用中:
C#复制
<PackageReference Include="Huitian.PowerCollections" Version="1.0.0" NoWarn="NU1701" />
如果现在重新运行应用程序,就再也不会看到警告了。如果安装另一个使用兼容性模式的包,也会看到可以取消的相应包警告。
使用新工具,还可以让类库项目创建属于生成的 NuGet 包。这样一来,可以更轻松地与全世界共享库(通过推送到 nuget.org),也可以仅在组织内共享(通过推送到大家在 Visual Studio Team Services 或 MyGet 上自己的包源)。新项目还支持多重定目标,这样就可以生成一个适用于多个 .NET 实现的项目。也就是说,可以使用条件编译 (#if) 来调整库,使其适应特定的 .NET 实现。还可以生成平台专属 API 的 .NET Standard 包装器。不过,这些都不在本文的讨论范围之内。
结束语
.NET Standard 是所有 .NET 实现都必须提供的 API 规范。这样一来,可以让 .NET 系列保持一致,并生成能够从任何 .NET 实现引用的库。它取代了生成共享组件的 PCL。
.NET Core 是更适合使用 ASP.NET Core 生成控制台应用程序、Web 应用程序和云服务的 .NET Standard 实现。它的 SDK 随附功能非常强大的工具,除了支持 Visual Studio 开发外,还支持基于命令行的完整开发工作流。