.NET 中的引用程序集

.NET 中的引用程序集

Intro

在 .NET 里有一种特殊的程序集叫做 ReferenceAssembly(引用程序集),引用程序集(Reference Assemblies) 是一种特殊类型的程序集,它只包含表示库的公共 API 所需的最少元数据量。它们包括在生成工具中引用程序集时所需的所有成员的声明,但不包括所有成员实现以及对其 API 协定没有明显影响的私有成员的声明。相比较下,常规程序集称为“实现程序集” (implementation assemblies)。

Why

既然我们有实现程序集,为什么还要有引用程序集呢?

使用引用程序集,开发人员可以生成面向特定库版本的程序,而无需具有该版本的完整实现程序集。因为不包含实现,引用程序集会更小一些,加载和解析都会更快一些。

这类似于我们和第三方的开发者约定的 API 规范,我们可以先给出 API 的请求和响应而无需提供实现以不 block 第三方开发者的进度,毕竟他们只关心 API 是什么样的而不关心实现。

若要使用项目中的某些 API,必须添加对其程序集的引用。可以将引用添加到实现程序集,也可以将其添加到引用程序集。建议在引用程序集可用时使用它。这样做可确保仅使用目标版本中受支持的 API 成员,即供 API 设计人员使用。使用引用程序集可确保不依赖于实现详细信息。

How

在 .NET Core 3.0 之前很多程序集都是发布 NuGet 包的,对于 .NET Core 3.0 和更高版本,核心框架的引用程序集位于 Microsoft.NETCore.App.Ref 包中,一般情况下是不需要的,因为引用程序集也会随着 .NET SDK 一起发布,你可以在 SDK 的安装目录下的 packs 目录下找到对应框架版本的引用程序集

下面是我电脑上 SDK 里的框架引用程序集的一个示例

e33d14914f0311e045251c5a4705d800.png

对于引用程序集只能用于编译,这种程序集会有一些特殊,反编译的话会看到有一个 ReferenceAssembly 的程序集 Attribute,下面是我从上面的目录中找的 System.Text.Json 的反编译结果,可以看到有一个 ReferenceAssembly 的 attribute

eb8c292fc03a659acb8013251e9677fc.png

Reference Assembly

再看一下 JsonNode 的实现

36faea8fbc53acd924e979b959d2bf22.png

我们再找一个实现的程序集对比一下

48f44a202e4fb06f85451ab8779d3308.png

Implementation assembly

7155d1597c2babfe5ff2ead1d84d8047.png

由于它们不包含任何实现,因此无法加载引用程序集用于执行。如果尝试这样做,则会导致 System.BadImageFormatException,可能会遇到 Reference assemblies can only be loaded in the Reflection-only loader context. 这样的错误。

如果要检查引用程序集的内容,你可将其加载到 .NET Framework 中的仅反射上下文中(使用 Assembly.ReflectionOnlyLoad 方法),或者加载到 .NET Core 中的 MetadataLoadContext。

More

经常看源码的童鞋,一定会注意到,dotnet/runtime 中很多的类库的结构都是类似下面这样的

892c15a41d1cb30d603f1bd570693406.png

runtime library structure

大家会看到第一个目录是 ref,也就是用来生成引用程序集的,src 则是包含了实现的项目源码,test 则是一些测试用例 https://github.com/dotnet/runtime/blob/89962a54d60e4d9c9837012d1729c5a72ec748cd/src/libraries/Microsoft.Extensions.Configuration/

ref 项目引用的其他项目也都是直接引用的 ref 项目 https://github.com/dotnet/runtime/blob/89962a54d60e4d9c9837012d1729c5a72ec748cd/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.csproj

b8180959d2d1647740951e92a9bf6d2f.png

查看 ref 项目的代码,可以发现和反编译的效果是一样的,都是空实现或者 throw null https://github.com/dotnet/runtime/blob/89962a54d60e4d9c9837012d1729c5a72ec748cd/src/libraries/Microsoft.Extensions.Configuration/ref/Microsoft.Extensions.Configuration.cs#L7

e4744be61635dfe58f8241803c5d863b.png

最近在做 dotnet-exec 这个小工具的时候就遇到了引用程序集的问题,起初没怎么理解这个引用程序集,在编译代码时使用的是引用程序集,在执行代码时也是用的引用程序集,在执行时 load 程序集的时候就报了前面提到的

BadImageException Reference assemblies can only be loaded in the Reflection-only loader context.

在看到 Youtube 上这个介绍 Reference Assembly 的视频(https://www.youtube.com/watch?v=EBpY1UMHDY8&list=PLRAdsfhKI4OX1cBGL2IXuEq1yzpDyKlwf&index=1&t=3s)之后才恍然大悟,原来如此。。。虽然视频是以 .NET Framework 为例讲解的,.NET Core 也类似,感兴趣的可以看一下

在 VS 里经常会遇到 F12 之后看到的实现都是 throw null,猜测也是因为这个原因,在编译时 VS 使用的是引用程序集来提高性能

最后有没有好奇 ref 项目和 src 项目的差别在哪里?表面上看 ref 项目文件里的东西好像没什么特别的啊,利用了之前我们提到过的 Directory.Build.props 来为大多数项目统一配置了,感兴趣的同学可以根据下面的链接自己探索一下

https://github.com/dotnet/runtime/blob/89962a54d60e4d9c9837012d1729c5a72ec748cd/src/libraries/Directory.Build.props#L8

https://github.com/dotnet/runtime/blob/89962a54d60e4d9c9837012d1729c5a72ec748cd/eng/referenceAssemblies.props#L22

3f24f9880353669a6f018c6b3eb4a27d.png

References

  • https://github.com/dotnet/docs/pull/14393

  • https://github.com/dotnet/docs/issues/2638

  • https://github.com/dotnet/roslyn/blob/main/docs/features/refout.md

  • https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-assemblies

  • https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/reference-assemblies

  • https://www.youtube.com/watch?v=EBpY1UMHDY8&list=PLRAdsfhKI4OX1cBGL2IXuEq1yzpDyKlwf&index=1&t=3s

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值