何时创建和分发“参考程序集”?

Imr*_*vel 4 c# .net-assembly roslyn c#-7.1

C# 7.1引入了一些新的命令行参数来帮助创建“参考程序集”。通过文档,它输出一个程序集,其中:

将它们的方法体替换为单个 throw null 体,但包括除匿名类型之外的所有成员。

我发现了一个有趣的注释,它在变化时更加稳定

这意味着它的更改频率低于完整的程序集——许多常见的开发活动不会更改接口,只会更改实现。这意味着增量构建可以更快 - ...

对于罗斯林本身来说可能是必要的..

我们将介绍第二个概念,即“参考组件”(也称为骨架组件)。[---]它们将用于构建场景。

.. 无论这些“构建场景”对于罗斯林来说是什么。

据我所知,对于普通的 .NET 程序集用户来说,此类程序集可能更小,并且加载反射的速度稍快。好的但是:

  • 通常您还关心执行,并且实现程序集已经包含参考程序集的所有数据,
  • 通常你并不关心加载时的微小性能差异,
  • 最重要的是 - 通常您根本没有可用(分布式)的精简参考程序集。

它的用处似乎相当小众。

因此,我想知道通用程序集生成器方面的事情 - 什么时候应该考虑显式使用这些新的编译器标志来创建引用程序集?除了罗斯林本身之外,它还有任何实际用途吗?

小智 5

此功能的动机确实是构建场景,但它们并不是 Roslyn 特有的;它们也是您的构建场景

当您构建项目时,构建引擎 (MSBuild) 需要确定构建的每个输出是否相对于其输入是最新的。例如,如果您不更改任何内容,只是连续运行两次构建,则第二次不需要调用 C# 编译器:程序集已经正确。

参考程序集允许在更多场景中跳过程序集的编译步骤,因此您的构建速度可以更快。我认为一个例子有助于说明。

假设您有一个包含B.exe依赖于 的解决方案A.dll

B 的编译器命令行看起来像

csc.exe /out:B.exe /r:..\A\bin\A.dll Program.cs
Run Code Online (Sandbox Code Playgroud)

它的输入是

  • B 的来源 ( Program.cs)
  • A 的装配。

如果更改 A 的源并构建解决方案,编译器必须为 A 运行,生成新的A.dll. 然后,由于A.dll是 B 编译的输入,因此 B 也必须重新编译。

使用 A 的参考组件会稍微改变这一点

csc.exe /out:B.exe /r:..\A\bin\ref\A.dll Program.cs
Run Code Online (Sandbox Code Playgroud)

A 的输入现在是其参考程序集,而不是其实现/正常程序集。

由于参考程序集比完整程序集小,因此它本身对构建时间的影响较小。但这还不足以证明此功能的合理性。重要的是编译器只关心传入引用的公共 API 表面。如果程序集的内部实现细节已更改,则无需重新编译引用它的程序集即可采用新行为。正如 @Hans Passant 在评论中提到的,这就是 .NET Framework 本身如何在未更改的用户代码上提供兼容的性能改进和错误修复。

参考程序集功能的好处来自于使用它们所做的 MSBuild 工作。假设您更改 A 中的内部实现细节,但不更改其公共接口。在下一个构建中,

  • 编译器必须针对 A 运行,因为 A 的源文件已更改。
  • 编译器同时发出A.dll(使用更改后的实现) 和ref\A.dll,这与之前的参考程序集相同。
  • 由于ref\A.dll与之前的输出相同,因此不会将其复制到 A 的输出文件夹。
  • 当 B 的编译器运行时,它发现它的输入没有任何改变——既没有改变 B 自己的代码,也没有改变 A 的引用程序集,因此编译器不必运行。
  • 然后,B 将更新的内容复制A.dll到其输出,并准备好以新行为运行。

当您在大型解决方案中进行时,跳过下游编译的效果可能会更加复杂——更改注释{ProjectName}.Utilities.dll不再需要构建所有内容!

许多更改涉及更改公共 API 表面和内部实现,因此此更改不会加快所有构建的速度,但确实会加快许多构建的速度。