使用跨平台工具开发库Developing Libraries with Cross Platform Tools

本文内容

本文介绍如何使用跨平台 CLI 工具编写 .NET 的库。CLI 提供可跨任何支持的 OS 工作的高效低级别体验。仍可使用 Visual Studio 生成库,如果你首选这种体验,请参阅 Visual Studio 指南

系统必备Prerequisites

需要在计算机上安装 .NET Core SDK 和 CLI

对于本文档中处理 .NET Framework 版本的部分,需要在 Windows 计算机上安装 .NET Framework

此外,如果想要支持较旧的 .NET Framework 目标,需要从 .NET 下载存档页安装用于较旧 Framework 版本的目标包/开发人员工具包。请参阅此表:

.NET Framework 版本下载内容
4.6.1.NET Framework 4.6.1 目标包
4.6.NET Framework 4.6 目标包
4.5.2.NET Framework 4.5.2 开发人员工具包
4.5.1.NET Framework 4.5.1 开发人员工具包
4.5适用于 Windows 8 的 Windows 软件开发工具包
4.0Windows SDK for Windows 7 和 .NET Framework 4
2.0、3.0 和 3.5.NET Framework 3.5 SP1 运行时(或 Windows 8+ 版本)

如何以 .NET Standard 为目标How to target the .NET Standard

如果对 .NET Standard 不是很熟悉,请参阅 .NET Standard 了解详细信息。

在该文中,提供有一个将 .NET Standard 版本映射到各种实现的表格:

.NET Standard1.01.11.21.31.41.51.62.02.1
.NET Core1.01.01.01.01.01.01.02.03.0
.NET Framework 14.54.54.5.14.64.6.14.6.1 24.6.1 24.6.1 2N/A3
Mono4.64.64.64.64.64.64.65.46.4
Xamarin.iOS10.010.010.010.010.010.010.010.1412.16
Xamarin.Mac3.03.03.03.03.03.03.03.85.16
Xamarin.Android7.07.07.07.07.07.07.08.010.0
通用 Windows 平台10.010.010.010.010.010.0.1629910.0.1629910.0.16299待定
Unity2018 年 1 月2018 年 1 月2018 年 1 月2018 年 1 月2018 年 1 月2018 年 1 月2018 年 1 月2018 年 1 月待定

1 针对 .NET framework 列出的版本适用于 .NET Core 2.0 SDK 和更高版本的工具。旧版本对 .NET Standard 1.5 及更高版本使用了不同映射。如果无法升级到 Visual Studio 2017,可下载适用于 Visual Studio 2015 的 .NET Core 工具

2 此处所列的版本表示 NuGet 用于确定给定 .NET Standard 库是否适用的规则。虽然 NuGet 将 .NET Framework 4.6.1 视为支持 .NET Standard 1.5 到 2.0,但使用为从 .NET Framework 4.6.1 项目构建的 .NET Standard 库存在一系列问题。对于需要使用此类库的 .NET Framework 项目,建议将项目升级到面向 .NET Framework 4.7.2 或更高版本。

3 .NET Framework 不支持 .NET Standard 2.1 或更高版本。有关更多详细信息,请参阅 .NET Standard 2.1 公告

  • 列表示 .NET Standard 版本。每个标题单元格都是一个文档链接,其中介绍了相应版本的 .NET Standard 中新增了哪些 API。
  • 行表示不同的 .NET 实现。
  • 各单元格中的版本号指示要定向到此 .NET Standard 版本所需的最低 实现版本。
  • 有关交互式表的信息,请参阅 .NET Standard 版本以下是此表格对于创建库的意义:

选择 .NET Standard 版本时,需要在能够访问最新 API 与能够定位更多 .NET 实现代码和 .NET Standard 版本之间进行权衡。通过选择 netstandardX.X 版本(其中 X.X 是版本号)并将其添加到项目文件(.csproj.fsproj),控制可面向的平台和版本范围。

面向 .NET Standard 时,有三种主要选项,具体取决于你的需求。

  • 可使用 .NET Standard 的默认版本,该版本由 netstandard1.4 模板提供,可提供对 .NET Standard 上大多数 API 的访问权限,同时仍与 UWP、.NET Framework 4.6.1 和即将推出的 .NET Standard 2.0 兼容。
  1. <Project Sdk="Microsoft.NET.Sdk">
  2. <PropertyGroup>
  3. <TargetFramework>netstandard1.4</TargetFramework>
  4. </PropertyGroup>
  5. </Project>
  • 可通过修改项目文件 TargetFramework 节点中的值来使用更低或更高版本的 .NET Standard。

.NET Standard 版本可后向兼容。这意味着 netstandard1.0 库可在 netstandard1.1 平台以及更高版本上运行。但是,不可向前兼容,即版本较低的 .NET Standard 平台无法引用版本较高的平台。这意味着 netstandard1.0 库不能引用面向 netstandard1.1 或更高版本的库。选择适合所需、恰当混合有 API 和平台支持的 Standard 版本。目前,我们建议 netstandard1.4

  • 如果希望面向 .NET Framework 版本 4.0 或更低版本,或者要使用 .NET Framework 中提供但 .NET Standard 中不提供的 API(例如 System.Drawing),请阅读以下部分,了解如何设定多目标。

如何以 .NET Framework 为目标How to target the .NET Framework

备注

这些说明假定计算机上安装有 .NET Framework。请参阅先决条件 获取安装的依赖项。

请记住,此处使用的某些 .NET Framework 版本不再受支持。有关不受支持的版本信息,请参阅 .NET Framework 支持生命周期策略常见问题

如果要达到最大数量的开发人员和项目,可将 .NET Framework 4.0 用作基线目标。若要以 .NET Framework 为目标,首先需要使用与要支持的 .NET Framework 版本相对应的正确目标框架名字对象 (TFM)。

.NET Framework 版本TFM
.NET Framework 2.0net20
.NET Framework 3.0net30
.NET Framework 3.5net35
.NET Framework 4.0net40
.NET Framework 4.5net45
.NET Framework 4.5.1net451
.NET Framework 4.5.2net452
.NET Framework 4.6net46
.NET Framework 4.6.1net461
.NET Framework 4.6.2net462
.NET Framework 4.7net47
.NET Framework 4.8net48

然后将此 TFM 插入项目文件的 TargetFramework 部分。例如,以下是如何编写面向 .NET Framework 4.0 的库:

  1. <Project Sdk="Microsoft.NET.Sdk">
  2. <PropertyGroup>
  3. <TargetFramework>net40</TargetFramework>
  4. </PropertyGroup>
  5. </Project>

就是这么简单!虽然此库仅针对 .NET Framework 4 编译,但可在较新版本的 .NET Framework 上使用此库。

如何设定多目标How to Multitarget

备注

以下说明假定计算机上安装有 .NET Framework。请参阅先决条件部分,了解需要安装哪些依赖项以及在何处下载。

如果项目同时支持 .NET Framework 和 .NET Core,可能需要面向较旧版本的 .NET Framework。在此方案中,如果要为较新目标使用较新的 API 和语言构造,请在代码中使用 #if 指令。可能还需要为要面向的每个平台添加不同的包和依赖项,以包含每种情况所需的不同 API。

例如,假设有一个库,它通过 HTTP 执行联网操作。对于 .NET Standard 和 .NET Framework 版本 4.5 或更高版本,可从 System.Net.Http 命名空间使用 HttpClient 类。但是,.NET Framework 的早期版本没有 HttpClient 类,因此可对早期版本使用 System.Net 命名空间中的 WebClient 类。

项目文件可能如下所示:

  1. <Project Sdk="Microsoft.NET.Sdk">
  2. <PropertyGroup>
  3. <TargetFrameworks>netstandard1.4;net40;net45</TargetFrameworks>
  4. </PropertyGroup>
  5. <!-- Need to conditionally bring in references for the .NET Framework 4.0 target -->
  6. <ItemGroup Condition="'$(TargetFramework)' == 'net40'">
  7. <Reference Include="System.Net" />
  8. </ItemGroup>
  9. <!-- Need to conditionally bring in references for the .NET Framework 4.5 target -->
  10. <ItemGroup Condition="'$(TargetFramework)' == 'net45'">
  11. <Reference Include="System.Net.Http" />
  12. <Reference Include="System.Threading.Tasks" />
  13. </ItemGroup>
  14. </Project>

在此处可看到三项主要更改:

  • TargetFramework 节点已替换为 TargetFrameworks,其中表示了三个 TFM。
  • net40 目标有一个 <ItemGroup> 节点,拉取一个 .NET Framework 引用。
  • net45 目标中有一个 <ItemGroup> 节点,拉取两个 .NET Framework 引用。生成系统可识别以下用在 #if 指令中的处理器符号:
目标框架符号
.NET FrameworkNETFRAMEWORK, NET20, NET35, NET40, NET45, NET451, NET452, NET46, NET461, NET462, NET47, NET471, NET472, NET48
.NET StandardNETSTANDARD, NETSTANDARD1_0, NETSTANDARD1_1, NETSTANDARD1_2, NETSTANDARD1_3, NETSTANDARD1_4, NETSTANDARD1_5, NETSTANDARD1_6, NETSTANDARD2_0, NETSTANDARD2_1
.NET CoreNETCOREAPP, NETCOREAPP1_0, NETCOREAPP1_1, NETCOREAPP2_0, NETCOREAPP2_1, NETCOREAPP2_2, NETCOREAPP3_0

以下是使用每目标条件编译的示例:

  1. using System;
  2. using System.Text.RegularExpressions;
  3. #if NET40
  4. // This only compiles for the .NET Framework 4 targets
  5. using System.Net;
  6. #else
  7. // This compiles for all other targets
  8. using System.Net.Http;
  9. using System.Threading.Tasks;
  10. #endif
  11. namespace MultitargetLib
  12. {
  13. public class Library
  14. {
  15. #if NET40
  16. private readonly WebClient _client = new WebClient();
  17. private readonly object _locker = new object();
  18. #else
  19. private readonly HttpClient _client = new HttpClient();
  20. #endif
  21. #if NET40
  22. // .NET Framework 4.0 does not have async/await
  23. public string GetDotNetCount()
  24. {
  25. string url = "https://www.dotnetfoundation.org/";
  26. var uri = new Uri(url);
  27. string result = "";
  28. // Lock here to provide thread-safety.
  29. lock(_locker)
  30. {
  31. result = _client.DownloadString(uri);
  32. }
  33. int dotNetCount = Regex.Matches(result, ".NET").Count;
  34. return $"Dotnet Foundation mentions .NET {dotNetCount} times!";
  35. }
  36. #else
  37. // .NET 4.5+ can use async/await!
  38. public async Task<string> GetDotNetCountAsync()
  39. {
  40. string url = "https://www.dotnetfoundation.org/";
  41. // HttpClient is thread-safe, so no need to explicitly lock here
  42. var result = await _client.GetStringAsync(url);
  43. int dotNetCount = Regex.Matches(result, ".NET").Count;
  44. return $"dotnetfoundation.org mentions .NET {dotNetCount} times in its HTML!";
  45. }
  46. #endif
  47. }
  48. }

如果使用 dotnet build 生成此项目,则在 bin/ 文件夹下有三个目录:

  1. net40/
  2. net45/
  3. netstandard1.4/

其中每个目录都包含每个目标的 .dll 文件。

如何在 .NET Core 上测试库How to test libraries on .NET Core

能够跨平台进行测试至关重要。可使用现成的 xUnit 或 MSTest。它们都十分适合在 .NET Core 上对库进行单元测试。如何使用测试项目设置解决方案取决于解决方案的结构下面的示例假设测试和源目录位于同一顶级目录下。

备注

此示例将使用某些 .NET Core CLI 命令有关详细信息,请参阅 dotnet newdotnet sln

  • 设置解决方案。可使用以下命令实现此目的:
  1. mkdir SolutionWithSrcAndTest
  2. cd SolutionWithSrcAndTest
  3. dotnet new sln
  4. dotnet new classlib -o MyProject
  5. dotnet new xunit -o MyProject.Test
  6. dotnet sln add MyProject/MyProject.csproj
  7. dotnet sln add MyProject.Test/MyProject.Test.csproj

这将创建多个项目,并一个解决方案中将这些项目链接在一起。SolutionWithSrcAndTest 的目录应如下所示:

  1. /SolutionWithSrcAndTest
  2. |__SolutionWithSrcAndTest.sln
  3. |__MyProject/
  4. |__MyProject.Test/
  • 导航到测试项目的目录,然后添加对 MyProject 中的 MyProject.Test 的引用。
  1. cd MyProject.Test
  2. dotnet add reference ../MyProject/MyProject.csproj
  • 还原包和生成项目:
  1. dotnet restore
  2. dotnet build

备注

从 .NET Core 2.0 SDK 开始,无需运行 dotnet restore,因为它由所有需要还原的命令隐式运行,如 dotnet newdotnet builddotnet run。在执行显式还原有意义的某些情况下,例如 Azure DevOps Services 中的持续集成生成中,或在需要显式控制还原发生时间的生成系统中,它仍然是有效的命令。

  • 执行 dotnet test 命令,验证 xUnit 是否在运行。如果选择使用 MSTest,则应改为运行 MSTest 控制台运行程序。

就是这么简单!现在可以使用命令行工具跨所有平台测试库。若要继续测试,现已设置好了所有内容,测试库将非常简单:

  • 对库进行更改。
  • 使用 dotnet test 命令在测试目录中从命令行运行测试。调用 dotnet test 命令时,将自动重新生成代码。

如何使用多个项目How to use multiple projects

对于较大的库,通常需要将功能置于不同项目中。

假设要生成一个可以惯用的 C# 和 F# 使用的库。这意味着库的使用者可通过对 C# 或 F# 来说很自然的方式来使用它们。例如,在 C# 中,了能会这样使用库:

  1. using AwesomeLibrary.CSharp;
  2. public Task DoThings(Data data)
  3. {
  4. var convertResult = await AwesomeLibrary.ConvertAsync(data);
  5. var result = AwesomeLibrary.Process(convertResult);
  6. // do something with result
  7. }

在 F# 中可能是这样:

  1. open AwesomeLibrary.FSharp
  2. let doWork data = async {
  3. let! result = AwesomeLibrary.AsyncConvert data // Uses an F# async function rather than C# async method
  4. // do something with result
  5. }

这样的使用方案意味着被访问的 API 必须具有用于 C# 和 F# 的不同结构。通常的方法是将库的所有逻辑因子转化到核心项目中,C# 和 F# 项目定义调用到核心项目的 API 层。该部分的其余部分将使用以下名称:

  • AwesomeLibrary.Core - 核心项目,其中包含库的所有逻辑
  • AwesomeLibrary.CSharp - 具有打算在 C# 中使用的公共 API 的项目
  • AwesomeLibrary.FSharp - 具有打算在 F# 中使用的公共 API 的项目可在终端运行下列命令,生成与下列指南相同的结构:
  1. mkdir AwesomeLibrary && cd AwesomeLibrary
  2. dotnet new sln
  3. mkdir AwesomeLibrary.Core && cd AwesomeLibrary.Core && dotnet new classlib
  4. cd ..
  5. mkdir AwesomeLibrary.CSharp && cd AwesomeLibrary.CSharp && dotnet new classlib
  6. cd ..
  7. mkdir AwesomeLibrary.FSharp && cd AwesomeLibrary.FSharp && dotnet new classlib -lang F#
  8. cd ..
  9. dotnet sln add AwesomeLibrary.Core/AwesomeLibrary.Core.csproj
  10. dotnet sln add AwesomeLibrary.CSharp/AwesomeLibrary.CSharp.csproj
  11. dotnet sln add AwesomeLibrary.FSharp/AwesomeLibrary.FSharp.fsproj

这将添加上述三个项目和将它们链接在一起的解决方案文件。创建解决方案文件并链接项目后,可从顶级还原和生成项目。

项目到项目的引用Project-to-project referencing

引用项目的最佳方式是使用 .NET Core CLI 添加项目引用。在 AwesomeLibrary.CSharp 和 AwesomeLibrary.FSharp 项目目录中,可运行下列命令:

  1. dotnet add reference ../AwesomeLibrary.Core/AwesomeLibrary.Core.csproj

AwesomeLibrary.CSharp 和 AwesomeLibrary.FSharp 的项目文件现在需要将 AwesomeLibrary.Core 作为 ProjectReference 目标引用。可通过检查项目文件和查看其中的下列内容来进行验证:

  1. <ItemGroup>
  2. <ProjectReference Include="..\AwesomeLibrary.Core\AwesomeLibrary.Core.csproj" />
  3. </ItemGroup>

如果不想使用 .NET Core CLI,可手动将此部分添加到每个项目文件。

结构化解决方案Structuring a solution

多项目解决方案的另一个重要方面是建立良好的整体项目结构。可根据自己的喜好随意组织代码,只要使用 dotnet sln add 将每个项目链接到解决方案文件,就可在解决方案级别运行 dotnet restoredotnet build