.NET 5 中 Target Framework 詳解

精緻碼農發表於2020-09-23

作者:.NET Team
翻譯:精緻碼農-王亮
原文:http://dwz.win/Q4v

我們希望極大地簡化開發人員必須在專案檔案和 NuGet 包中使用的TFM (Target Framework Name, 目標框架名稱)。這包括合併 .NET 5 和 .NET Standard 的概念,同時仍然可以使用 #if 來編寫特定於作業系統的程式碼。本文解釋了開發的動機和由此產生的開發者體驗。

.NET 和大多數有二十年曆史的技術一樣,有很多歷史遺留問題,特別是在產品命名和版本方面:.NET Framework, .NET Compact Framework, Silverlight, .NET Micro Framework, .NET Portable Class Libraries, .NET for Windows Store, .NET Native, .NET Core, .NET Standard 等等,這還不包括 Mono 系的產品。雖然 .NET 的這種演變是情有可原的,但它創造了一個巨大的學習成本:不計其數的概念。如果你是 .NET 的新手,你會從哪裡開始?最新技術棧是什麼?你可能會說,當然那是 .NET Core 了,但是新手們怎麼可能只看名字就知道它是什麼?

我們已經用 .NET Standard 簡化了世界。在類庫中,作者不必考慮用不同的“盒子”去包裝不同的 .NET 實現。這是因為我們為不同的 .NET 實現統一了上層 API。具有諷刺意味的是,這導致我們不得不新增另一個大“箱子”,即 .NET Standard。

為了使未來的 .NET 生態更加健康地發展,我們必須減少“包裝盒”的數量。我們也不想讓 .NET 變得不那麼靈活,但是我們想減少純粹因為我們沒有儘早地開源而導致的荒謬差異。例如,Mono/Xamarin/Unity 與 .NET Framework/Silverlight/UWP/.NET Core 基於不同的執行時和框架。我們已經開始使用 .NET Standard 消除 API 表面上的差異。.NET 5 的目標是將這些產品線聚合到單個產品技術棧上,從而統一它們的實現。

雖然我們在努力為開發者提供良好的開發體驗,讓你不必對不同種類的 .NET 編寫不同的實現。但我們仍然不想完全抽象掉底層的作業系統,所以你將繼續能夠呼叫作業系統特定的 API,無論是通過 P/Invokes、WinRT, 還是 Xamarin 為 iOS 和 Android 提供的繫結。

現在想想那些開始使用這個技術棧的開發者,可以為 .NET 提供支援的任何平臺編寫任何應用程式,他們需要的是更快的找到文件和教程。為此,他們只需要知道兩件事,就是他們的技術棧名稱和版本。

讓我們看看目前這是一個什麼樣的體驗,以幾個比較流行的 NuGet 包為例,作者必需編寫:

有很多名稱和版本號。如果沒有“解碼環”(譯註:一種比喻),想知道誰與誰相容是不可能的。我們已經用 .NET Standard 大大簡化了這一點,但這仍然需要一個對映表將 .NET Standard 版本和 .NET 實現版本進行匹配。

我們的提議是通過新的語法重新使用現有的 net TFM 和作業系統特定的 API 模型:

net5.0,這個 TFM 是表示程式碼可以在任何環境執行,它結合並取代了 netcoreappnetstandard 的名稱。這個 TFM 一般只包括跨平臺的技術(像我們已經在 .NET Standard 中做的那樣)。

net5.0-androidnet5.0-iosnet5.0-windows,這些 TFM 代表了 .NET 5 的特定作業系統,包括 net5.0 加上特定作業系統的繫結。

NuGet 應該使用這種新的語法來自動理解:在 net6.0-windows 中可以使用 net5.0(而反過來不行)。更重要的是,這種符號還能讓開發人員直觀地理解相容性關係。

場景和使用者體驗

不同的實現

小花正在開發一個支援 Android、iOS 和 Windows 的 Xamarin Forms 應用程式。她的應用需要獲取 GPS 資訊,但只是針對非常有限平臺。由於沒有可移植的 GPS API,她使用 multi-target 寫了自己的小抽象庫。

通過這種方式,她能夠封裝 GPS 訪問,而不必對整個應用進行 multi-target,只需在一個地方進行 multi-target 即可。

public static class GpsLocation
{
    public static bool IsSupported
    {
        get
        {
#if ANDROID || IOS || WINDOWS
            return true;
#else
            return false;
#endif
        }
    }

    public static (double Latitude, double Longitude) GetCoordinates()
    {
#if ANDROID
        return AndroidAPI();
#elif IOS
        return AppleAPI();
#elif WINDOWS
        return WindowsAPI();
#else
        throw new PlatformNotSupportedException();
#endif
    }
}

不同的 API

小花是 SkiaSharp 的開發者,SkiaSharp 是一個基於谷歌 Skia 圖形庫的 .NET 跨平臺 2D 圖形 API。該專案已經在使用 multi-target 來為不同平臺提供不同的實現。為了讓它更容易使用,她增加了一個新的 SkiaSharpImage 型別,它代表一個點陣圖,並通過 OS 提供的資料型別來構造。小花使用 #if 在不同平臺上暴露不同的建構函式:

public class SkiaSharpImage
{
#if ANDROID
    public SkiaSharpImage(Android.Media.Image nativeImage) { /* ... */  }
#endif

#if IOS
    public SkiaSharpImage(NSImage nativeImage) { /* ... */ }
#endif

#if WINDOWS
    public SkiaSharpImage(Windows.Media.BitmapImage nativeImage) { /* ... */ }
#endif
}

更新 OS 繫結

小明正在構建一個叫 Baby Shark 的 iOS 應用。他一開始使用的是支援 iOS 13 的 .NET,但蘋果剛剛釋出了 iOS 14。他下載了更新版的 .NET 5 SDK,它包含了對 iOS 14 的支援。為了獲得蘋果新增的新 API 的訪問支援,小明開啟了他的專案檔案,目前這個檔案是這樣的:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0-ios13.0</TargetFramework>
  </PropertyGroup>

  ...

</Project>

他把 <TargetFramework> 修改為 net5.0-ios14.0

點亮新的 OS 版本

小明不想切斷目前使用 iOS 13 的使用者,所以他希望自己的應用也能繼續在 iOS 13 上執行。為了達到這個目的,小明修改了專案檔案,新增了 <SupportedOSPlatformVersion>

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0-ios14.0</TargetFramework>
    <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
  </PropertyGroup>

  ...

</Project>

不過,由於小明也使用了蘋果在 iOS 14 中加入的新的 NSFizBuzz API,所以他也修改了自己的原始碼,在呼叫之前檢查 OS 版本:

public void OnClick(object sender, EventArgs e)
{
    if (Environment.OSVersion.Version >= new Version(14, 0))
    {
        NSFizBuzz();
    }
}

消費更高的 SupportedOSPlatformVersion 庫

在直接使用 NSFizzBuzz 一段時間後,小明注意到這些作業系統的 API 有點難用,於是他開始尋找一個.NET 庫。他找到了 Monkey.FizzBuzz,並嘗試引用它,成功了。然而,當編譯他的應用程式時,他得到以下警告:

warning NU1702: Package 'Monkey.FizzBuzz' was restored using 'net5.0-ios14' and has 'SupportedOSPlatformVersion' of '14.0' while the project has a value of '13.0'. You should either upgrade your project to '14.0' or only make calls into the library after checking that the OS version is '14.0' or higher.

由於小明已經對所有的方法呼叫進行了保護,所以他只是取消了警告(譯註:可以在專案檔案中通過<NoWarn>設定)。

消費更高的 TargetPlatformVersion 庫

小明在他的 Baby Shark 應用中成功使用 Monkey.FizzBuzz 後,小明想在其它地方也使用它,所以他決定在他現有的 Laserizer 5000 應用中使用它。然而,當他新增對 Monkey.FizzBuzz 的引用時,他得到一個 NuGet 引用錯誤:

error NU1202: Package 'Monkey.FizzBuzz' is not compatible with 'net5.0-ios13.0'. Package 'Monkey.FizzBuzz' supports: net5.0-ios14.0

所以小明修改了他的專案檔案,將 net5.0-ios13.0 改為 net5.0-ios14.0,從而解決了這個錯誤。

使用比當前 SDK 更高的 TargetPlatformVersion

小翠的環境安裝的是第一個版本的 .NET 5 SDK,它只提供到 iOS 13 的支援。她從 GitHub 上克隆了小明的 Baby Shark 倉庫,並試圖在她的機器上編譯它。由於 Baby Shark 的目標是 net5.0-ios14.0,她得到了一個編譯錯誤:

error NETSDK1045: The current .NET SDK does not support targeting iOS 14.0. Either target iOS 13.0, or use a version of the .NET SDK that supports iOS 14.0. [BabyShark.csproj]

要求

目標

  • 使用與產品戰略一致的命名;
  • 將 .NET Core 和 .NET Standard 合併為一個概念;
  • 開發人員應該能夠理解相容性關係,而不必查閱對映表;
  • 提供與現有概念和 NuGet 包的相容性;
  • 如果能在 .NET 5 的早期預覽版中加入這個功能就再好不過了;
  • 支援同一作業系統的不同版本的 multi-target;
  • 不強迫同一作業系統應用不同版本的 multi-target。當呼叫被作業系統的檢查保護時,應當能夠產生一個可以使用較新 API 的二進位制檔案。

非目標

  • 換 TFM 或擴大執行時識別符號(RID)

未完待續

譯註:文章太長,今天先翻譯一半,有空再繼續翻譯。後一半主要講 TFM 的設計細節,比如多個 TFM 選擇的優先順序、 MSBuild 的屬性(TFI、TFV、TFP、TPI、TPV 等)、NuGet 包的行為等。其中比較重要的是下面這張表,它列出了現有的 TFM,我覺得大家有必要了解一下:

相關文章