包,元包与框架
本文翻译自 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。