包,元包與框架
本文翻譯自 Packages, Metapackages and Frameworks。
.Net Core 是一種由 NuGet 包組成的平台。一些產品體驗受益於代碼包的細粒度定義,而另一些受益於粗粒度的定義,這兩種定義都是有用的,不能絕對地說哪個好與不好。因此,為了適應這兩種區別,一款好的產品應該可以被拆分成一組一組的細粒度的代碼包,這些包之間相互獨立,單個代碼包的正式的名字叫做“元包”(metapackage)。
每個.Net Core 包都支持運行於多種 .Net 運行時中,這些運行時被稱為 “框架”(framework)。其中有的框架是傳統框架,例如 net46;而另一些則是新的框架,可以認為它們是“基於包的框架”,這種是框架的另外一種新的定義模型。這些基於包的框架整個都是由包組成的,它們自身也被定義成包,這就在框架與包之間形成了一種比較密切的關系。
包
.Net Core被分割成為一組包,它們提供了基元類型,以及更高層的數據類型,應用組合類型,以及公共的類庫。每一個包都代表着單獨的同名程序集,例如,System.Runtime 這個包,就包含了 System.Runtime.dll 這個程序集。
把這些包定義成為細粒度的結構風格,有以下好處:
- 細粒度的包可以在它自己的開發周期內交付,只需要完成僅對相關的其他有限的包進行測試即可。
- 細粒度的包可以提供不同的OS與CPU支持。
- 細粒度的包可以單獨依賴於某一個類庫。
- 應用可以變得更小,因為沒有引用的包不會變成最終發布的一部分。
上述好處僅適用於某些特定的場合。比如,Net Core 的所有包會在同一個發布周期內提供對同一個平台的支持,在這種情況下,補丁與更新會以小的單獨包的形式發布。由於這種小范圍的變化,補丁的驗證與相關工作,都可以限制到單個平台與類庫的需求范圍中,這樣一來,就可以把工作最小化。
以下是 基於 NuGet 庫的 .Net Core 包清單:
- Sytem.Runtime - 最基礎的 .Net Core 包,包含 Object,String,Array, Action 與 IList<T>。
- System.Collections - 一組基元泛型集合,包含 List<T> 與 Dictionary<K,V>.
- System.Net.Http - 一組用於 HTTP 網絡通信的類型,包含 HttpClient 與 HttpResponseMessage。
- System.IO.FileSystem - 一組用於讀寫本地或者網絡磁盤存儲的類型,包含了 File 與 Dictory 類。
- System.Linq - 一組對象查詢類型,包含了 Enumerable 與 ILookup<TKey, TElement> .
- System.Reflection - 一組用於類型加載,檢查與激活的類型,包含 Assembly, TypeInfo, 與 MethodInfo
包引用在 project.json 文件中定義,在下面的例子中,使用了 System.Runtime 包:
{ "dependencies": { "System.Runtime": "4.1.0" }, "frameworks": { "netstandard1.5": {} } }
在大部分情況下,你可能不需要直接引用底層的 .Net Core 包,因為引用的包太多了會讓你抓狂。所以你只需要引用元包。
元包
元包就是一個 NuGet 包,方便地描述了一組意義相關的包。開發團隊利用依賴關系來描述一組包,他們通過這一組包來描述一個框架(framework),然后有選擇地發布出去。
引用一個元包,這個操作,實際上添加了對元包中每一個獨立包的引用依賴,這意味着這些包中所有的類庫(refs 或者 libs)都會在智能感知(IntelliSense)中可用,同時也會發布(lib目錄)到你的應用中。
注意: lib 與 ref 指的是 NuGet 包中的相應的文件夾, ref 目錄描述的是以元程序集表示的公共API包,lib 目錄 包含了這個公共API的在不同框架下的實現。
使用元包有以下好處:
- 在引用大量細粒度包方面,提供了一種方便的用戶體驗。
- 定義了一組經過充分測試與工作良好的包(包括指定的各種版本)。
.Net 標准類庫元包:
- NETStandard.Library - 描述了“.Net 標准類庫”的一部分。所有的 .Net 實現(例如, .Net Framework, .Net Core, Mono)都支持 .Net 標准類庫,這就是 'netstandard' 框架。
以下是 .Net Core 元包:
- Microsoft.NETCore.App - 描述了 .Net Core 發行版本的部分類庫,也就是 .NETCoreApp 框架。它依賴於NETStandard.Library 這個更小的元包。
- Microsoft.NETCore.Portable.Compatibility - 一組兼容代理,使基於 mscorlib 的 可移植類庫(PCL) 得以運行在 .Net Core上。
元包的引用方法就像普通的 NuGet包一樣,在 project.json 中定義。
在下面的例子中, 引用了 NETStandard.Library 這個元包,用來創建一個基於 .Net 運行時的可移植類庫。
{ "dependencies": { "NETStandard.Library": "1.5.0" }, "frameworks": { "netstandard1.5": {} } }
在下面的例子中,引用了 Microsoft.NETCore.App 這個元包,用來創建應用或者是類庫,運行於 .Net Core之上,並且充分使用 .Net Core 所有功能。它提供的訪問范圍,要比 NetStandard.Library 更大。
{ "dependencies": { "Microsoft.NETCore.App": "1.0.0" }, "frameworks": { "netcoreapp1.0": {} } }
框架
每一個 .Net Core 包都支持一組框架,在框架文件夾中進行聲明(就在前面所說的 lib 與 ref目錄中)。框架描述了一組可用的API(以及潛在的其他特性),所以你可以在指定一個目標框架時得到這些功能。當新的API加入時,就會進入版本管理流程。
例如, System.IO.FileSystem 支持以下框架:
- .NETFramework,版本 4.6
- .NETStandard,版本 1.3
- 6種Xamarin 平台 (如, xamarinios10)
很有必要對前兩種框架進行對比,因為它們各自代表了一種不同的框架定義方式。
.NETFramework,Version=4.6 這個框架代表了在 .Net Framework 4.6 中可用的API,你可以根據這些API生產自己的類庫,引用其中的程序集,並以NuGet包的方發布你的類庫,發布后它們會位於lib文件夾下一個名為 net46 的文件夾中。這樣,你的
類庫就會被那些基於或者兼容 .Net Framework 4.6 的應用程序所使用。這是傳統類庫的工作方式。
而.NETStandard,Version=1.3這個框架是一個基於包的框架。那些以此框架為目標的包,定義並且實現的API,就組成了這個框架。
可見它們的區別:傳統的框架是事先定義好的一個整體,而基於包的框架,則可以對不同的包自由組合。
基於包的框架
框架與包之間的關系是雙向的。
首先是為一個給定的框架定義了一組API,如 netstandard1.3。以 netstandard1.3 為目標的包(或者兼容框架,如netstandard1.0)定義了哪些API是可以使用的,聽起來像是循環定義,然而並不是。從 “基於包的”這個詞本身的角度來講,框架的API定義是來自於包的,框架本身並不定義任何API。
其次,是這個雙向關系中的資產選擇。包內部可以含有不同框架的資產,對於一組包或者元包的引用,框架需要決定它應選擇哪些資產,例如,是 net46 還是 netstandard1.3。對於交叉資產來說,這個非常重要,例如,一個 net46 的資產可能並不會與 .Net Framework 4.0 或者 .Net Core 1.0 兼容。
你可以在上圖中看到這種關系,API 選擇 框架 作為目標,並且API定義了框架, 而框架用於資產選擇,資產實現了API。
這里出現了一個有趣的問題:框架定義的結束之處,正是消費開始的地方。可以把框架看成是由 project.json 文件給出的功能定義,你所依賴的東西創建了實際上的框架,這個框架獨立於那些已經發布出來的完整框架的依賴項。(譯者注:可以這么理解,
框架的實際實現取決於你在 project.json 中引用的東西,可能你並不會引用所有的包,所以你所依賴的包是官方框架的全部包的一個子集。)
在.Net Core基礎之上,基於包的框架主要有兩個:
- netstandard
- netcoreapp
.NET Standard
.NET 標准框架(netstandard)意指基於 .Net 標准類庫所定義與構建的API。如果你所構建的類庫將會用於多種運行平台,應該以基於netstandard進行構建,這樣類庫就會支持任何一種 兼容 .Net 標准的運行時,比如 .Net Core, .Net Framework以及 Mono/Xamarin。這些運行時中的每一處都支持一種或幾種 .Net 標准,至於到底支持哪個版本,則取決於具體實現。
元包 NETStandard.Library 的目標框架是 netstandard。要以 netstandard 為目標框架,最常見的方法是引用此元包。它定義與提供了約40個.Net類庫,並與.Net 標准類庫所定義的API相關聯。你可以引用基於 netstandard 開發的第三方包來使用第三方API。
一個給定的 NETStandard.Library 版本,總是與 netstandard 所公開的最高版本相匹配。 project.json 文件中對於框架的引用,主要是用來從此框架中選擇正確的資產。因此,假如定義了 netstandard1.5,就需要 其中的dll資產,而不是 netstandard1.4,或者 net46。
{ "dependencies": { "NETStandard.Library": "1.5.0" }, "frameworks": { "netstandard1.5": {} } }
這個 project.json 文件中所引用的 框架與元包 並不需要嚴格匹配,例如下面的 project.json 也是正確的:
{ "dependencies": { "NETStandard.Library": "1.5.0" }, "frameworks": { "netstandard1.3": {} } }
把構建目標設置為 netstandard1.3 卻使用 NETStandard.Library 的 1.5 版本,似乎有點奇怪。然而這是一個合法的用例,因為元包支持更老的 netstandard 版本。可能恰好 你使用了 1.5.0 的元包版本來開發你所有的類庫,然后運行於多種版本的 netstandard,在這種情況下,你只需要重新加載 NETStandard.Library 1.5.0 即可,並不需要加載早期版本。
反之則並不成立:把 netstandard1.5 作為運行目標,卻使用 NETStandard.Library 的 1.3.0 版本來開發你的類庫:你不能夠把更高版本的框架作為開發目標,卻使用更低版本的元包。元包資產的版本管理機制,與框架的最高版本的定義相匹配。借助於版本管理機制,NETStandard.Library 的第一個版本是 v1.5.0,它包含了 netstandard1.5 的資產。而上面例子中的 v1.3.0 版本,只是為了舉例方便,實際上並不存在。
譯者注:這一段的各種名詞相互繞來繞去,會把人繞暈。舉個例子就明白了:因為類庫總是向下兼容的,1.5 的實現必然包含了1.3的所有定義,1.5 版本的元包,是可以運行於 1.3 版本的框架之上。
然而這與我們的直覺經驗不符,因此作者說看起來很奇怪,因為你在 Win7 上開發的程序(依賴高版本元包),很可能不支持運行在 XP 系統上(低版本框架)。
但是為什么在神奇的 .Net Core 的世界中,這個現象就發生了呢?
這是由 .Net Core 的版本管理機制所決定的。文中並沒有給出來具體的解釋。關於版本管理,有另外一篇文章會介紹,稍后翻譯。
.NET Core Application
.NET Core Application 框架(netcoreapp) 意為: 包與相關的API是 基於.Net Core 特定發布版本以及它所提供的控制台程序模型。.Net Core應用程序必須使用這個框架,因為必須要使用其中的控制台程序模型。同時只運行於 .Net Core 平台的類庫也應使用這個模型。使用這框架以后,應用程序(exe)與類庫(dll)將只能夠運行於.Net Core平台上。(老外好啰嗦啊) 元包 Microsoft.NETCore.App 的目標框架是 netcoreapp 。此元包提供了約 60 個類庫,其中大約 40 個是由 NETStandard.Library 提供的,另外20個是由 Microsoft.NETCore.App 自己實現的(addition)。如果你開發的類庫的目標框架是 netcoreapp 或其兼容框架(如netstandard),則可以使用 Microsoft.NETCore.App 的這些20個額外類庫(addition),來調用這些額外的API。
譯注:addition,是指由 Microsoft.NETCore.App 在 NETStandard.Library 基礎之上的額外實現,約20個類庫。在從傳統 Framework遷移過來的代碼中,如果你使用了 NETStandard.Library ,可能會出現不識別類或者方法的情況,很有可能是因為這些不識別的部分實現在這20個addition中,改為引用 Microsoft.NETCore.App 可能會解決問題。
由 Microsoft.NETCore.App 額外實現的大部分類庫,也可以運行於 其他的 netstandard 框架,如果這些框架滿足了它們所依賴的類庫的運行環境的話。這意味着 運行於 netstandard 框架的類庫也引用這些額外包作為依賴項。
譯注:這段話也比較晦澀,再舉例子說明。例如 Microsoft.NETCore.App 1.5 (簡稱App1.5)是 在 NETStandard.Library 1.5 (簡稱Lib1.5)之上實現了額外20個包(Add20),即 App1.5 = Lib1.5 + Add20。 前面說 Lib1.5 可以運行於框架 netstandard 1.5(簡稱 Std1.5),以及 Std1.3 之上,如果這個 Add20 也可以運行於 Lib1.3 的話,那么就可以得到 App1.5 也可以運行於 Lib1.3。