標籤:

【譯】介紹 .NET Standard

原文:Introducing .NET Standard

作者:Immo Landwerth

若有任何對翻譯的建議,煩請指正

有任何問題?請查閱 .NET Standard FAQ。

在我上一篇博文中,我曾提到我們想要使遷移到 .NET Core 更容易。在這篇博文中,我將專註於我們是怎樣使用 .NET Standard 以實現這個計劃的。我們將會涉及到諸如哪些API將被包含,跨平台兼容性是怎樣工作的,以及這一切對於 .NET Core 意味著什麼等內容。

如果你對細節感興趣,那麼這篇文章很適合你。但如果你沒空或對細節不感興趣,別擔心:你可以只看「長話短說」這一節。

給趕時間的人:長話短說

.NET Standard 通過引入在桌面應用,移動應用和遊戲以及雲服務等不同環境中 .NET 開發者所期待和喜愛的nAPI 以解決跨平台的代碼共享問題:

  • .NET Standard 是一組所有n .NET 平台都應實現的 API。這將統一 .NETn 平台,並預防將來的碎片化。
  • .NET Standard 2.0 將會被 .NET Framework,.NET Core,和 Xamarin 所實現。對於 .NET Core 來說,將會新增許多被要求加入的(.NET Framework)現有的API。
  • .NET Standard 2.0 包含一個 .NET Framework 二進位文件的兼容層,可以顯著增加 .NETn Standard 庫可以引用的庫的數量。
  • .NET Standard 將替代可移植類庫(PCLs) 成為跨平台 .NET 庫的開發工具。
  • 你可以在 Github 的倉庫 dotnet/standard 上看到 .NET Standard API的定義。

為什麼我們需要一個標準?

在博文介紹 .NET Core 中我們曾詳細介紹過,.NET 平台在過去幾年中產生了許多分支。一方面來說,這實際上是件好事。它使你可以裁剪n.NET 以滿足單一的平台所不能滿足的需求。例如,.NET Compact Framework 就是為了滿足 2000 年前後資源受限的手機的需求而生的。在今天,(這種做法)同樣適用:Unityn(Mono 的一個分支)在超過 20 個平台上運行。能夠創建新分支和可定製性是任何技術都需要的一項重要能力。

但是在另一方面來說,這種行為為開發人員編寫跨 .NET 平台的代碼帶來了巨大的麻煩,因為沒有統一的類庫可用:

目前主要有三種版本的n.NET,這意味著你需要掌握三種不同的基礎類庫以寫出可以在三種平台上運行的代碼。由於業界與n.NET 剛建立之時相比更加的多樣化,可以想像我們的工作將隨著新的 .NET 平台的創建一直進行下去。無論是 Microsoft 還是任何(有這類想法的)人,都可以構建一個新的n.NET 版本以支持新的操作系統或是去裁剪它以適應特定設備的配置。

這是 .NET Standard 將要處在的位置:

對於開發者而言,這意味著他們只需要掌握一種基礎類庫。以n.NET Standard 作為目標平台的類庫將可以在所有 .NETn平台上運行。同時,平台提供者也無需猜測為了使用 NuGet 上的庫,他們需要提供哪些nAPI 。

應用程序。在應用程序這個層次,你不需要直接使用 .NET Standard。然而,你仍將間接受益。首先,.NETnStandard 保證所有的 .NET 平台使用同樣功能的基礎類庫。如果你學會了如何在桌面應用中使用它,你同時也學會了如何在移動應用和雲服務中使用它。其次,使用n.NET Standard 將使大多數類庫可以被用在任何地方,這意味著基層的一致性也將擴散至更大的 .NETn生態系統。

可移植類庫。讓我們來和現在的可移植類庫的運作方式做個比較。使用可移植類庫,你選擇你想發布的平台,然後工具將展示所有你可用的nAPI。所以儘管工具幫助你使類庫運行在不同平台,它仍然迫使你去考慮不同的基礎類庫。而n.NET Standard 只有一個基礎類庫。它的全部內容都將被所有 .NET 平台支持——無論是現在還是將來。另一個重要方面,API 的可用性是非常可預測的:版本越高,API 越多。而對可移植類庫來說,這不是必然的:可用的nAPI 集合是所選平台的 API 集合的交集,這通常會生成一個你無法輕易預料的nAPI 層。

API 的連貫性。 如果你對比n.NET Framework,.NET Core,和nXamarin/Mono,你會注意到 .NET Core 提供了最小規模的nAPI 層(不包含 OS 相關的 API)。首要的不一致之處就是基礎 API 可用性的重大差異(例如網路和加解密的nAPI)。第二個 .NET Core 導致的問題是核心部分nAPI 的不一致,尤其是反射。這兩個不一致之處是導致向 .NET Core 遷移的難度高於預期的主要原因。通過建立n.NET Standard,我們正在編寫在所有 .NET 平台擁有統一的nAPI 的要求,這同時包括了API 的可用性和一致性(方面的要求)。

版本和工具。 正如我在介紹 .NET Core 一文中所提到的,我們對於 .NET Core 的目標,是為一個可統一 APIn和實現,可移植的 .NET 平台打下基礎。我們打算讓其成為可移植類庫的下一個版本。不幸的是,它的工具使用體驗並不是很好。由於我們的目標是代表任意一個n.NET 平台,我們將它拆成了更小的 NuGet 包。如果這些組件都能隨著應用程序被部署,則它們表現的非常好,因為你可以單獨的更新這些組件。然而,當你指定了一個抽象規範,例如可移植類庫或n.NET Standard,這件事就沒那麼美好了,因為為了可以運行在正確的平台集上,需要非常具體的版本組合。為了避免這種情況,我們將n.NET Standard 定義為一個單獨的 NuGet 包。由於它只表示所需的一組nAPI,(我們)不需要再去將它們拆分,因為所有的 .NET 平台都必須原生的支持這個(包)。唯一重要的維度是它的版本,和nAPI 等級類似:版本越高,API 越多,而版本越低,則越多 .NETn平台實現了它。

總結一下,我們需要 .NET Standard 有兩個理由:

  1. 對一致性的動力。 我們想達成一個共識,所有 .NET 平台都必須實現一組必需的 API 才可以獲得進入 .NET 庫生態系統的許可權。
  2. 優秀的跨平台工具的基礎。 我們希望一個簡化的工具體驗,你只需要選擇一個版本號來指定所有 .NET 平台的通用部分。

.NET Standard 2.0 有哪些更新?

當我們發布 .NET Core 1.0 時,我們同時介紹了 .NETnStandard。.NET Standard 有許多版本,以表示這套nAPI 在現有的各個平台上的可用性。下表展示了哪些現有平台的版本與指定 .NET Standard 版本相兼容:

箭頭表示此平台支持更高等級的n.NET Standard。例如,.NET Core 1.0n支持 1.6 版的 .NET Standard,這就是 1.0 版 到 1.5 版是箭頭的原因。

你可以藉助這張表來分析你的目標平台可用的最高版本的n.NET Standard 是哪個版本。例如,如果你想在n.NET Framework 4.5 和 .NET Core 1.0 上運行,你最高只能將目標平台設為n.NET Standard 1.1。

你也可以了解哪些平台將支持n.NET Standard 2.0:

  • 我們即將發布新增了所有支持 .NET Standard 2.0 所必需API的 .NET Core,Xamarin和 UWP 的一個升級版本。
  • .NET Framework 4.6.1 已經實現了所有屬於n .NET Standard 2.0 的 API。請注意,這個版本出現了兩次;我會在稍後解釋為什麼以及它是如何運作的。

.NETnStandard 同時也兼容可移植類庫。從可移植類庫的配置到 .NET Standard 版本的映射已在我們的文檔中列出。

在一個目標平台為 .NET Standard 的庫中你將可以引用兩種類型的庫:

  • .NET Standard,如果庫的版本低於或等於你的目標版本。
  • 可移植類庫,如果庫的配置文件可以被映射到一個低於或等於你指定的 .NET Standard 版本。

如圖所示:

不幸的是,NuGet 上對可移植類庫和 .NET Standard 的接受程度並不是很高,以至於沒有達到可以順暢使用他們的地步。下面是指定目標平台在 NuGet.org 上的包中出現的次數:

如你所見,顯然大部分nNuGet 上的類庫以 .NET Framework 作為目標平台。然而,我們了解到這些庫中有很大一部分只使用了我們將在 .NET Standard 2.0 中列出的 API。

在 .NET Standardn2.0中,我們將使在 .NET Standard 庫中通過一個兼容層去引用已有的n.NET Framework 的庫成為可能:

當然,這隻對那些只使用了適用於n.NET Standard API 的 .NETnFramework 庫有效。這也是為什麼不推薦以這種方式構建跨 .NET 平台庫的理由。然而,這個兼容層也為你將自己引用了尚未轉換的庫的庫轉換成n.NET Standard 庫提供了一座橋樑。

如果你想了解更多關於這個兼容層是怎樣工作的,請查閱 .NET Standard 2.0 的規範。

.NET Standard 2.0 中的重大更新:增加對 .NETnFramework 4.6.1 的兼容

一個標準只有在有平台實現了它之後才有用。我們想同時讓n.NET Standard 本身變得有意義和有用,因為它是適用於那些以標準作為目標平台的庫的nAPI 層:

  • .NET Framework. .NET Framework 4.6.1 有最高的接受度,這使它成為最引人注意的目標 .NETn Framework 版本。因此,我們想保證它可以實現 .NET Standard 2.0 。
  • .NET Core. 正如上面所提到的,.NET Core 有一個比n .NET Framework 或 Xamarin 都小得多的n API 集。(使它)支持 .NET Standard 2.0 意味著我們要顯著擴展它的n API 範圍。由於 .NET Core 並不是隨操作系統而是隨應用發行的,只需要更新n SDK 和我們的 NuGet 包就可以支持 .NETn Standard 2.0。
  • Xamarin. Xamarin 已經支持大部分 .NET Standard 的 API。(它的)更新將類似於 .NET Core——我們希望更新 Xamarin 以補全所有目前缺失的 API。事實上,大部分缺失的 API 已經在 the stablen Cycle 8 release/Mono 4.6.0 中被添加了。

之前列出的表格展示了哪個版本的 .NET Framework 支持哪個版本的 .NET Standard:

按照通常的版本規則,可以預期n.NET Standard 2.0 將只會被更高的 .NETnFramework 版本所支持,目前最新的 .NET Framework 版本(4.6.2)只支持 .NET Standard 1.5。這意味著在 .NETnStandard 2.0 之前編譯的庫將不能在大部分(包含) .NET Framework 設備上運行。

為了使 .NET Frameworkn4.6.1 支持 .NET Standard 2.0,我們必須將所有 .NET Standard 1.5 和 1.6 引入的 API 移除。

你可能會好奇這個決定所造成的影響是什麼。我們對所有 NuGet.org 上的包進行了一次分析,觀察它們是否以 .NETnStandard 1.5 或更高為目標版本並調用了這些 API。在寫這篇文章時,我們只發現了n6 個非 Microsoft 的包這樣做了。我們將聯繫這些包所有者並與他們一同解決這個問題。通過檢查它們的使用方式,(我們發現)顯然這些調用可以被n.NET Standard 2.0 中引入的 API 所取代。

為了讓這些包的作者支持n.NET Standard 1.5,1.6 和 2.0,他們需要明確的指定這些版本並交叉編譯。另外,他們可以選擇指定 .NETnStandard 2.0 和更高版本,(這將)提供一個廣泛的支持的平台集。

.NET Standard 有哪些內容?

我們使用以下幾個步驟來決定哪些 API 將成為 .NET Standard 的一部分:

  • 輸入。 我們從 .NETn Framework 和 Xamarin 都有的n API 開始入手。
  • 評估。 我們把這些 API 分成以下兩類:

1. 必需的。 對於那些我們希望所有平台都提供且我們認為可以被跨平台實現的nAPI,我們標記為 必需的。

2. 可選的。 對於那些平台相關的或是遺留技術,我們標記為 可選的。

可選的 API 將不會成為 .NET Standard 的一部分,但將以獨立的 NuGet 包的形式提供。我們儘可能的將 .NETnStandard 作為這些庫的目標平台以使它們可以在任意平台上使用,但對平台相關的 API 來說不一定行得通(例如 Windows 註冊表)。

為了使一些 API 成為可選的,我們可能需要移除部分必需的 API。例如,我們判定 AppDomian 應該在 .NET Standard 中而代碼訪問安全性(CAS)是遺留組件。為此我們需要移除 AppDomain 中所有使用了 CAS 的類型的成員,例如 CreateDomain 的接受證據的重載方法。

.NETnStandard 的 API 集以及我們的可選 API 目標都可以在 .NET Standard』s review body 處審閱。

這是 .NET Standard 2.0 API 層的高度概括:

如果你想查閱 .NETnStandard 2.0 的具體 API,你可以在 .NET Standard Github 代碼倉庫處查閱。請注意,.NET Standard 2.0 是一項正在進行的工作,這意味著有些nAPI 可能會新增,同時有些 API 會被刪除。

我還能使用平台相關的 APIn嗎?

在建立適用於多平台類庫的經驗時遇到的最大挑戰之一就是避免只擁有最不通用的分母(共通處),同時還要確保你不會意外創建比你預期的更加難以移植的庫。

在可移植類庫中我們通過擁有不同的、代表一系列平台的(API 的)交集的配置文件解決了這個問題。好處是你可以儘可能的在一組目標平台間擴展你的nAPI 集。.NET Standard 表示一組所有 .NETn平台都必須實現的 API。

這帶來了關於我們是如何為無法在所有平台上實現的 API 建模的問題:

  • 運行時相關的 API。 例如,在運行時通過反射生成和運行代碼的能力。這項特性無法在沒有n JIT 編譯器的 .NET 平台上工作,例如 UWPn 的 .NET Native 或 Xamarin 的 iOS 工具鏈。
  • 操作系統相關的 API。 在 .NET 中我們封裝了許多 Win32n API 以便於使用。正如 Windows 註冊表。(它們的)實現依賴於底層的n Win32 API,而在別的平台上沒有等價物。

關於這些 API,我們有一些選項:

  1. 使這些 API 失效。 你無法使用那些不能跨 .NET 平台運行的n API。
  2. 保持這些 API 可用,但拋出 PlatformNotSupportedException。 這意味著無論這些 API 是否被所有平台支持,我們都將其暴露出來。不支持這些n API 的平台提供它們,但是(調用時)會拋出 PlatformNotSupportedException。
  3. 模擬這些 API。 Mono 在 .ini 文件的基礎上實現註冊表。儘管對於那些使用註冊表讀取系統信息的應用不起作用,它仍在應用程序只是簡單的儲存自身的狀態和用戶設置的情景下工作的很好。

我們相信最好的選項是將它們結合起來。就如我們之前提到過的,我們希望n.NET Standard 代表一組所有 .NET 平台都必須實現的 API。我們希望使這組 API 易於實現,同時確保受歡迎的 API 存在以使編寫跨平台的庫輕鬆和直觀。

我們處理只在部分n.NET 平台上可用的技術的通常的做法是將它們做成基於 .NETnStandard 的 NuGet 包。因此如果你創建了一個基於n.NET Standard 的庫,默認情況下它不會引用這些 API。你需要添加nNuGet 包來引入它們。

這種做法對於那些獨立的的 API 來說很有效,因此(它們)可以被放進獨立的包中。對於那些類型中個別成員無法在所有平台實現的情況,我們將使用第二和第三種方法:平台必須有這些成員,但它們可以決定拋出(異常)或是模擬它們。

讓我們看看一些例子,以及我們是打算如何處理它們的:

  • 註冊表。 Windows 註冊表是一個以獨立 NuGet 包形式提供的獨立的的組件(例如 Microsoft.Win32.Registry)。你可以在 .NETn Core 中使用它,但它只在 Windows 上有作用。在其他平台調用註冊表的n API 將引發 PlatformNotSupportedException。你應該正確的保護你的調用,或是保證只會在 Windows 上運行。我們正在考慮改善我們的工具以幫助你檢測這類問題。
  • 應用程序域。 AppDomain 類型有許多不與創建應用程序域緊密相關的 API,例如列出所有被載入的程序集,或是註冊一個未處理異常的處理函數。這些 API 在n .NET 庫生態系統中經常被用到。基於這種情況,我們覺得最好將這個類型加入到 .NET Standard並使少部分與應用程序域的創建相關的 API 在不支持的平台上拋出異常,例如 .NETn Core。
  • 反射動態生成。 反射動態生成大致上獨立,因此我們計劃按照註冊表的思路來處理它。其他的一些 API (是否可用)取決於是否能供動態生成代碼,例如表達式樹的n Compile 方法,或是編譯正則表達式的功能。在部分情況下,我們將模擬這些行為(例如解釋執行而不是編譯表達式樹),其他情況下我們將拋出異常(例如編譯正則表達式的時候)。

一般來說,你可以像現在一樣指定特定的n.NET 平台來繞過這些在 .NET Standard 上不可用的 API。我們正在考慮改善我們的工具,以使從平台相關到平台無關的遷移更加順暢,這樣一來你們可以選擇最適合你們的情景的選項,而不會被早期的設計選項所困擾。

總而言之:

  • 我們會公布不一定在所有 .NET 平台上可用的想法。
  • 我們一般會將它們做成需要你們顯式引用的獨立的包。
  • 在極少數情況中,特殊的成員可能會拋出異常。

(我們的)目標是使基於 .NET Standard 的庫儘可能的強大和有豐富的表達力,同時保證在你依賴不一定在所有地方可用的技術時提示你。

這對n.NET Core 意味著什麼?

我們精心設計了 .NET Core 以使它的引用程序集成為可移植的 .NETn庫的範例(PS:此段翻譯不一定準確。如有更好的翻譯,歡迎指正)。這使得添加新的nAPI 變得困難,因為向 .NET Core 添加這些 APIn(的行為)提前決定了它們是否該在所有平台上可用。更壞的情形是,由於版本規則的限制,這也意味著我們需要決定哪些nAPI 組合該以怎樣的順序可用。

單獨分發。 我們試著通過將這些nAPI 做成基於現有 API 的新組件來使它們可被單獨分發。對於很容易這樣做的技術,這將是首選的方法,因為這也意味著任何 .NET 開發者可以與這些 API 打交道並向我們提供反饋。我們已經在不可變集合上(用這種方法)取得了巨大的成功。

推斷運行時特性。 然而,對於那些需要運行時支持的特性來說會很困難,因為我們不能僅僅給你一個可用的 NuGet 包。我們也需要提供一種獲得更新過的運行時的方法。這在擁有系統級運行時的平台上比較困難(例如n.NET Framework),在一般的平台上也很困難,因為我們有多種適用於不同目的的運行時(例如 JIT 和 AOT)。一次性引入所有這些方面是不現實的。.NET Core 的好處是這個平台被設計成完全獨立的。因此在未來,我們更有可能利用這種特性進行實驗和預覽。

.NET Core 中分離出n.NET Standard。 為了能獨立於其他 .NETn平台來發展 .NET Core,我們將 .NET Core 的可移植機制(我之前提到的那個)剝離了出來。.NET Standard 被定義為一個獨立的、適用於所有n.NET 平台的引用程序集。每個 .NET 平台使用一組不同的引用程序集,因此它們可以隨心所欲的添加新的nAPI。基於此,我們可以決定其中的哪些會被加入 .NET Standard 並變得通用。

從 .NET Core 分離出可移植機制幫助我們加速 .NETnCore 的開發,也使新特性的實驗變得更加簡單。我們可以簡單的修改那些需要被修改以支持新特性的層,而不是彆扭的嘗試去基於現有的平台來設計特性。我們也可以將nAPI 添加到它們邏輯上所屬的類,而不用去擔心這些類是否已被移植到了其他平台上。

在 .NET Core 中添加新的 API 並不是它們是否將會進入 .NET Standard 的聲明,我們對於 .NETnStandard 的目標是建立並維持 .NET 平台間的一致性。因此已經是標準的一部分的類的新成員將自動在標準更新的時候被考慮。

作為一名庫作者,我現在應該做什麼?

作為一名庫作者,你應該考慮遷移到n.NET Standard,因為它將在目標為多個平台的情景下取代可移植類庫。

在 .NET Standard 1.x 中可用的 API 集和可移植類庫非常相似。但 .NET Standard 2.x 將有一個顯著變大的nAPI 集,同時也允許你依賴以 .NET Framework 為目標平台的庫。

可移植類庫和 .NET Standard 的關鍵區別有:

  • 平台約束。 可移植類庫面臨的一大問題就是你指定多個平台時,它仍然是一組特定的 API。尤其對 NuGet 包來說更是如此,因為你必須在庫文件夾名中列出所有平台,例如 portable-net45+win8。在支持相同 API 的新的平台出現時,這種情況會導致問題。.NET Standard 不存在這種問題,因為你只是指定了一個不包含任何平台信息的標準版本,例如 netstandard1.4.
  • 平台可用性。 可移植類庫目前支持很多平台,而並不是每一個(平台的)配置文件都有一個匹配的n .NET Standard 版本。查閱此文檔以查看更多細節。
  • 庫可用性。 可移植類庫被設計成不允許依賴目標平台無法運行的 API 和庫(的樣子)。因此,可移植類庫僅允許你引用指定了你的可移植類庫所指定的平台的超集的可移植類庫。.NETn Standard 也類似,但它額外允許你引用目前 .NET 生態系統事實上的硬通貨——.NET Framework 的庫。因此,在 .NET Standard 2.0 中你將有一個更大的庫的集合。

為了做出明智的決定,我建議你:

  1. 使用 API 移植來檢查你的代碼庫與不同 .NET Standard 版本的兼容性如何。
  2. 通過查閱 .NET Standard 的文檔來保證你可以在對你來說較為關鍵的平台上運行。

例如,如果你想知道你是否需要等n.NET Standard 2.0 (的發布),你可以下載 API 移植命令行工具並以如下方式來將你的庫與 .NETnStandard 1.6 和 .NET Standard 2.0 對比:

apiport analyze -f C:srcmylibs -t ".NET Standard,Version=1.6"^ n -t ".NET Standard,Version=2.0"n

注意: .NET Standard 2.0 是一項正在進行的工作,因此 API 將可能會變動。同時我也提醒你們注意那些在 .NETnStandard 1.6 中存在但在 .NET Standard 2.0 中被移除的那些 API。

總結

我們已經建立了n.NET Standard,因此不同 .NET 平台間代碼的共享和復用將變得更加簡單。

在 .NET Standard 2.0 中,我們正專註於兼容性。為了在n.NET Core 和 UWP 中支持 .NET Standardn2.0,我們將擴展這些平台以包含更多的現有 API。這也包括了一個允許你引用為n.NET Framework 編譯的庫的兼容層。

在未來,我們建議你使用n.NET Standard 而不是可移植類庫。以 .NETnStandard 2.0 為目標的工具將與代號為「Dev 15「的nVisual Studio 一同發布。你將以 NuGet 包的形式引用 .NETnStandard。它將同時在 Visual Studio,VSnCode 和 Xamarin Studio 中獲得最優先的支持。

你可以通過關注我們全新的Github 代碼倉庫 dotnet/standard 來關注我們的進展。

請讓我們知道你的想法!

推薦閱讀:

妥協與取捨,解構C#中的小數運算
怎樣考察有八年經驗程序猿的水平(C#)?
如何評價 Unity 2018?
如何解決.NET程序容易被反編譯的問題?
剖析並利用Visual Studio Code在Mac上編譯、調試c#程序

TAG:C# | NET |