揭示.NET Core和.NET Standard
2017-09-23

作为.NET家族的最新成员,有很多关于.NET Core和.NET Standard的误解,以及它们于.NET Framework之间的区别。在这篇文章,我会准确的解释他们究竟是什么,并看看何时应选择哪一个。

在详细介绍之前,首先查看.NET的结构图,它将帮助我们更好的理解.NET Core和.NET Standard所在的未知。当15年前,.NET 框架第一版发布时, 它有一个单一的. NET 堆栈, 可用于构建 Windows 桌面和 Web 应用程序。从那时起,其他.NET 实现也开始出现,比如 Xamarin,使用Xamarin你可以为iOS和Android构建移动应用程序,以及macOS桌面应用程序,如图 1所示.
The .NET Landscape


图 1 .NET 结构图

关于.NET Core和.NET Standard如何融入.NET结构:

  • .NET Core: .NET Core是最新的一个.NET 实现。开放源代码,可用于多个操作系统。使用.NET Core,你可以构建跨平台控制台应用程序和 ASP.NET Core Web 应用程序和云服务。

  • .NET Standard: 这是所有的.NET实现所必须实现的基本Api (通常称为base class library或 BCL)。通过以.NET Standard为目标,您可以构建能够在所有. net 应用程序之间共享的库, 无论它们运行在哪个.NET实现或在哪个操作系统上。

.NET Core 简介

.NET Core是一个.NET Framework 和 Silverlight的一个分支,支持跨平台并且完全开源。它通过启用独立的 XCOPY 部署来优化移动和服务器工作负载。

为了更好的了解.NET Core,让我们详细查看基于.NET Core的开发是如何进行的,同时探索新的基于命令行的工具。你也可以使用 Visual Studio 2017来进行. NET Core开发,但因为你正在阅读本文,你很可能已经熟悉 Visual Studio,所以我将重点介绍新的体验。

当.NET 创建时, 它为 Windows 上的快速应用程序开发进行了大量优化。在实践中,这意味着,.NET 开发和 Visual Studio 是形影不离的好朋友。当然:使用 Visual Studio开发是一种冲击。它极为高效,并且调试器是我用过最好的工具。

但是, 在某些情况下, 使用 Visual Studio 并不是很方便。比如你只是想要简单的通过.NET学习 C#,你不应该下载并安装一个大型的IDE。或者你通过 SSH 访问 Linux 机器,此时无法使用IDE。或者你只是更喜欢使用命令行界面 (CLI)。

这就是名为.NET Core CLI被创建的原因。.NET Core CLI 的主要命令为dotnet。你可以将其用于开发的几乎所有方面, 包括创建、编译、测试和打包项目。让我们通过实例来了解它是如何操作的。

创建并运行一个 Hello World 控制台应用程序 (我在Windows系统使用PowerShell,但是同样可以在macOS或Linux的Bash中运行):

$ dotnet new console -o hello
$ cd hello
$ dotnet run
Hello World!

CLI中的 dotnet new 命令等于Visual Studio中的文件|新建项目。 你可以创建各种不同的项目类型。输入 dotnet new 以查看预安装的不同模板。

现在,让我们将一些逻辑提取到一个类库中。为此,需首先创建一个类库项目平行于你的 hello项目:

$ cd ..
$ dotnet new library -o logic
$ cd logic

你想要封装的逻辑是构建一个 Hello World 的消息,所以修改 Class1.cs 的内容为下面的代码:

namespace logic
{
    public static class HelloWorld
    {
        public static string GetMessage(string name) => $"Hello {name}!";`
    }
}

同时你还需要重命名 Class1.cs 为 HelloWorld.cs:

$ mv Class1.cs HelloWorld.cs

注意,您不需要由于此变化更新项目文件。使用在.NET Core的新项目文件会包含项目目录中的所有源文件。因此,添加、 删除和重命名文件并不需要再修改项目文件。这使得命令行文件操作更加顺畅。

若要使用 HelloWorld 类,您需要更新 Hello 应用程序引用的逻辑类库。你可以通过编辑项目文件,或者通过使用dotnet add reference命令:

$ cd ../hello
$ dotnet add reference ../logic/logic.csproj

现在,修改 Program.cs 文件以使用 HelloWorld 类,如下所示.

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:

$ dotnet run
What's your name: Immo
Hello Immo!

你还可以从命令行创建测试。CLI 支持 MSTest,以及流行的 xUnit 框架。本示例中使用xUnit:

$ cd ..
$ dotnet new xunit -o tests
$ cd tests
$ dotnet add reference ../logic/logic.csproj

改变 UnitTest1.cs 的内容,如下所示,添加一个测试。

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 运行测试:

$ dotnet test
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.

为了让事情更有趣一点,让我们创建一个简单的 ASP.NET Core Web 站点:

$ cd ..
$ dotnet new web -o web
$ cd web
$ dotnet add reference ../logic/logic.csproj

编辑 Startup.cs 文件,修改app.Run方法,调用 HelloWorld类:

app.Run(async (context) =>
{
  var name = Environment.UserName;
  var message = logic.HelloWorld.GetMessage(name);
  await context.Response.WriteAsync(message);
});

若要启动 Web 开发服务器,只需使用 dotnet run 命令:

$ dotnet run
Hosting environment: Production
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.

浏览到所显示的 URL,应该是 http://localhost:5000。

此时,你的项目文件夹结构应该看上去如下所示:

$ tree /f
│
├───hello
│ hello.csproj
│ Program.cs
│
├───logic
│ HelloWorld.cs
│ logic.csproj
│
├───tests
│ tests.csproj
│ UnitTest1.cs
│
└───web
Program.cs
Startup.cs
web.csproj

为了便于使用Visual Studio编辑文件的文件,让我们同样创建一个解决方案文件并向解决方案中添加的所有项目:

$ cd ..
$ dotnet new sln -n HelloWorld
$ ls -fi *.csproj -rec | % { dotnet sln add $_.FullName }

   正如你所看到的,.NET Core CLI 功能强大,来自其他背景的开发人员会产生非常熟悉的感觉。无论你使用在Windows PowerShell中使用dotnet还是在Linux或macOS中使用,这些平台的使用体验颇为相似。

   .NET Core的另一个巨大好处是它支持独立部署。你可以使用Docker,它具有其自己的.NET Core运行时副本。这使你可以在同一台机器使用不同版本的.NET Core而不互相干扰。因为.NET Core的开源特性,你还可以使用*nightly builds*甚至自己修改编译,包含自己所做修改的版本。当然,这已超出了这篇文章的讨论范围。
.NET 标准简介

当你构建现代应用程序时,您的应用程序往往跨平台或需要使用多个.NET 实现。当今时代,客户会期待他们可以在手机中使用 Web APP,数据通过基于云计算的后台服务共享。当使用笔记本电脑时,他们也会想要通过 Web 站点获取访问权限。对于自己的基础架构,你可能想要使用命令行工具,甚至可能通过桌面应用程序让您的员工管理系统。下面是不同.NET实现是如何实现这一目标的。

|           |  OS        |是否开源  |                   目的                              |
| .NET Framework | Windows     |   否   | 构建Windows应用程序,构建运行在IIS上的Web应用程序       |
| .NET Core    | Windows, Linux, macOS |  是  | 构建跨平台命令行应用程序、ASP.NET Core应用程序、云服务  |
| Xamarin     | iOS, Android, macOS  |   是 | 构建iOS、Android移动应用程序、macOS桌面应用程序        |
| .NET Standard     | N/A      |   是 | 创建可以被所有.NET实现(如.NET Core和.NET Framework)所引用的类库    |

在这种环境中,代码共享成为一项重大挑战。你需要理解 Api 是否可用,并确保共享的组件只使用在您正在使用的所有.NET 实现中可用的 Api。

这就是.NET Standard出现的原因,.NET Standard是一种规范,每个.NET Standard定义了所有.NET实现都必须提供的、符合该版本的Api集。你可以看作它然而另一个.NET 堆栈,只是你不能使用.NET Standard构建应用程序,而只能用来构建类库。这是您要从任何地方引用的库的. NET 实现。

你可能想知道.NET Standard包含了那些API,如果你熟悉.NET 框架,你应该熟悉我之前提到的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。事实上,你已经使用过了。还记得我们先前创建的逻辑类库吗?让我们仔细看看项目文件:

$ cd logic
$ cat logic.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

</Project>

让我们对比"Hello"控制台应用程序的项目文件:

$ cd ..\hello
$ cat hello.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\logic\logic.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 Standard的一部分。然而,更多的通用库只针对.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 Standard类库一样,添加.NET Framework的类库引用。

我会为你展示此操作。在我的示例中,我将使用写于2007年,名为PowerCollections的类库,此类库已经有一段时间未进行更新,并且仍然针对.NET Framework 2.0编译。我将通过NuGet安装这个类库到 Hello 程序中。

$ dotnet add package Huitian.PowerCollections

此库提供了BCL没有提供一些集合类型,比如 bag,。让我们修改 Hello 应用程序来使用它,如下所示.

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);
        }
    }
}

如果你运行该程序,您将看到以下内容:

$ dotnet run
hello.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.
1
3
2

所以,刚刚发生了什么?Hello应用程序的目标框架为.NET Core 2.0。由于.NET Core 2.0 实现.NET Standard 2.0,它同时具有对.NET Framework类库引用的兼容性模式。然而,并不是所有的.NET Framwork都能工作在所有的.NET实现中。例如,他们可能会使用 Windows Forms 或 WPF Api。NuGet则并不知道这些信息,因此,它给了你一条警告消息,似的你可以意识到这种情况,对由此造成的问题进行故障排除,减少排除故障的时间。

请注意,每次生成程序,你都将看到此警告。这避免了你在包安装过程中没有注意到警告,或者干脆忘记了这个警告。

当然,没有什么比每次生成程序时收到警告更糟糕的了。所以,这里的建议是,当你验证你的应用程序后,就可以可以禁用针对该软件包的警告。因为应用程序运行良好(它正确打印了您创建的bag中的内容),所以你现在可以禁止显示此警告。要做到这一点,编辑 hello.csproj 文件,并将 NoWarn 属性添加到包引用:

<PackageReference Include="Huitian.PowerCollections" Version="1.0.0" 
  NoWarn="NU1701" />

如果你现在再次运行该程序,应该就不会再看到此警告了。如果你使用兼容模式安装另一个NuGet包,你同样会收到针对另一个包的运行警告。

这款新工具还允许类库在生成时,将NuGet程序包作为生成的一部分。这使得你与世界 (使用nuget.org) 或只是在您的组织内分享你的类库 (通过发布自己的包到Visual Studio Team Services 或 MyGet)成为了一项简单的工作。新项目也支持多个目标框架,使得你在单个项目中针对多个.NET实现进行生成。这意味着您可以使用条件编译 (#if) 以适配类库到.NET 的特定实现。它还允许你为特定于平台的 Api 构建.NET Standard wrappers。然而,这些都超出了本文的范围。

  • 总结

.NET Standard规范了所有的.NET实现都必须提供的API。它为.NET家族带来了一致性,并使你能够生成可供所有.NET实现使用的类库。它取代了PCL来构建共享的组件。

.NET Core是.NET Standard的其中一个实现,为建立控制台应用程序、 Web 应用程序和使用ASP.NET Core的云服务进行了优化。它的SDK中附带了一个强大的工具,除使用Visual Studio进行开发外,还支持完整的基于命令行的开发流程。
你可以了解通过aka.ms/netstandardfaq 和 aka.ms/netcore 了解更多.


Immo Landwerth is a program manager at Microsoft, working on .NET. He focuses on .NET Standard, the BCL and API design.