跨平台定位

新式 .NET 支持多个操作系统和设备。 .NETNET 开源库支持尽可能多的开发人员(无论是构建 Azure 中托管的 ASP.NET 网站的开发人员,还是在 Unity 中开发 .NET 游戏的开发人员),这一点非常重要。

.NET 和 .NET Standard 目标

.NET 和 .NET Standard 目标是为 .NET 库添加跨平台支持的最佳方式。

  • .NET Standard 是一套 .NET API 规范,在所有 .NET 实现中推出。 以 .NET Standard 为目标可以生成受限于使用给定版本的 .NET Standard 中的 API 的库,这意味着实现该版本的 .NET Standard 的所有平台都可以使用它。
  • .NET 6-8 是 .NET 的实现。 每个版本是具有一组统一功能和 API 的单一产品,可用于 Windows 桌面应用和跨平台控制台应用、云服务和网站。

有关 .NET 与 .NET Standard 的对比的详细信息,请参阅 .NET 5 和 .NET Standard

.NET Standard

如果你的项目面向 .NET 或 .NET Standard 并成功编译,这不保证库在所有平台上都能成功运行:

  • 特定于平台的 API 将在其他平台上失败。 例如,Microsoft.Win32.Registry 可以在 Windows 上成功运行,但在任何其他操作系统上使用时,会引发 PlatformNotSupportedException
  • API 的行为可能有所不同。 例如,当应用程序在 iOS 或 UWP 上使用提前编译时,反射 API 具有不同的性能特征。

提示

.NET 团队提供平台兼容性分析器,帮助你发现可能存在的问题。

✔️ 请务必首先包含一个 netstandard2.0 目标。

最常规用途的库不需要 .NET Standard 2.0 以外的 API。 所有新式平台都支持 .NET Standard 2.0,并且它是支持具有一个目标的多个平台的推荐方法。 如果你不需要支持 .NET Framework,则也可以面向 .NET Standard 2.1。

✔️ 如果需要在新式 .NET 中引入的新 API,务必包含一个 net6.0(或更高版本)目标。

.NET 6 及更高版本的应用可以使用 netstandard2.0 目标,所以 net6.0 不是必需的。 当你想要使用较新的 .NET API 时,应显式面向 net6.0net7.0net8.0

❌ 请避免包含 netstandard1.x 目标。

.NET Standard 1.x 作为一组精细的 NuGet 包分发,它创建了一个大型的包依赖项关系图,并导致在构建时需下载大量的包。 新式 .NET 实现支持 .NET Standard 2.0。 如果需要专门面向较旧的平台,则只能面向 .NET Standard。

✔️ 如果需要面向 netstandard1.x 目标,请务必包括 netstandard2.0 目标。

所有支持 .NET Standard 2.0 的平台都将使用 netstandard2.0 目标,并从较小的包关系图中受益,而较旧的平台仍然可以正常运行并回退到使用 netstandard1.x 目标。

❌ 如果库依赖于平台特定的应用模型,请避免包括 .NET Standard 目标。

例如,UWP 控件工具包库依赖的应用模型只能在 UWP 上使用。 特定于应用模型 API 在 .NET Standard 中不可用。

多目标

有时,需要从库中访问特定于框架的 API。 调用特定于框架的 API 的最佳方法是使用多目标,它会为许多 .NET 目标框架(而不是仅为一个)构建项目。

为了让使用者不必针对各个框架构建,应该尽量获取 .NET Standard 输出以及一个或多个特定于框架的输出。 通过多目标,所有程序集都打包在一个 NuGet 包中。 然后,使用者可以引用同一个包,NuGet 将选择适当的实现。 你的 .NET Standard 库充当在任何地方使用的回退库(NuGet 包提供特定于框架的实现的情况除外)。 通过多目标可以在代码中使用条件编译并调用特定于框架的 API。

NuGet package with multiple assemblies

✔️ 请在面向 .NET Standard 的基础上考虑面向 .NET 实现。

通过面向 .NET 实现,可以调用 .NET Standard 范围之外的特定于平台的 API。

执行此操作时,请不要放弃对 .NET Standard 的支持。 相反,从实现引发并提供功能 API。 这样一来,你的库就可以在任何地方使用,并支持运行时启动功能。

public static class GpsLocation
{
    // This project uses multi-targeting to expose device-specific APIs to .NET Standard.
    public static async Task<(double latitude, double longitude)> GetCoordinatesAsync()
    {
#if NET462
        return CallDotNetFrameworkApi();
#elif WINDOWS_UWP
        return CallUwpApi();
#else
        throw new PlatformNotSupportedException();
#endif
    }

    // Allows callers to check without having to catch PlatformNotSupportedException
    // or replicating the OS check.
    public static bool IsSupported
    {
        get
        {
#if NET462 || WINDOWS_UWP
            return true;
#else
            return false;
#endif
        }
    }
}

✔️ 如果项目具有任何库或包依赖项,请考虑多目标,即使源代码对所有目标均相同。

在每个目标框架的不同依赖程序集版本中包装时,项目的依赖包(直接包或下游包)可以使用相同的代码 API。 添加特定目标可确保使用者无需添加或更新其程序集绑定重定向。

❌ 如果你的源代码对所有目标都相同且项目没有任何库或包依赖项,请避免使用多目标或面向 .NET Standard。

.NET Standard 程序集将自动供 NuGet 使用。 面向单个 .NET 实现会增加 *.nupkg 大小,不会提供任何好处。

✔️ 请考虑在提供 netstandard2.0 目标时为 net462 添加目标。

在 .NET Framework 中使用 .NET Standard 2.0 存在一些问题,这些在 .NET Framework 4.7.2 中已得到解决。 你可以为仍在使用 NET Framework 4.6.2-4.7.1 的开发人员提供为 .NET Framework 4.6.2 构建的二进制文件,从而改善其体验。

✔️ 请使用 NuGet 包分发库。

NuGet 将为开发人员选择最佳目标,使他们无需选择适当实现。

✔️ 请务必在使用多目标时使用项目文件的 TargetFrameworks 属性。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- This project will output netstandard2.0 and net462 assemblies -->
    <TargetFrameworks>netstandard2.0;net462</TargetFrameworks>
  </PropertyGroup>
</Project>

✔️ 请考虑在对 UWP 和 Xamarin 进行多目标定位时使用 MSBuild.Sdk.Extras,它可以极大地简化项目文件。

❌ 避免更改程序集名称或为库编译的每个 TFM 使用不同的程序集名称。 由于库之间的依赖关系,每个 TFM 具有不同程序集名称的多目标可能会中断包使用者。 程序集在所有 TTFM 中应具有相同的名称。

较旧的目标

.NET 支持面向不再受支持的 .NET Framework 版本以及不再经常使用的平台。 尽管使库在尽可能多的目标上工作是有价值的,但这种情况下必须解决缺少的 API 问题,这会增加很大的开销。 考虑到某些框架的使用范围和局限性,不再值得面向它们。

❌ 请避免包括可移植类库 (PCL) 目标。 例如,portable-net45+win8+wpa81+wp8

.NET standard 是支持跨平台 .NET 库的新式方法,它可以替代 PCL。

❌ 请避免包括面向不再受支持的 .NET 平台的目标。 例如:SL4WP