C# 7.1引入了一些新的命令行参数来帮助创建"引用程序集"。通过文档,它输出一个程序集,该程序集:
将其方法主体替换为单个抛出 null 主体,但包括除匿名类型之外的所有成员。
我发现了一个有趣的注释,它在更改时更稳定:
这意味着它的更改频率低于完整程序集 - 许多常见的开发活动不会更改接口,只会更改实现。这意味着增量构建可以更快 - ...
而且罗斯林本身可能是必要的.
我们将介绍第二个概念,即"引用程序集"(也称为骨架程序集)。[---]它们将用于生成方案。
..无论这些"构建场景"对罗斯林来说是什么。
我知道对于普通的 .NET 程序集用户,这样的程序集可能更小,加载以进行反射的速度略快。好的,但是:
- 通常你也关心执行,实现程序集已经包含来自引用程序集的所有数据,
- 很多时候,您并不关心加载时的微小性能差异,
- 最重要的是 - 通常您根本没有可用的(分布式)精简的参考程序集。
它的用处似乎相当小众。
所以,我想知道大会生产者方面的事情 - 什么时候应该考虑显式使用这些新的编译器标志来创建引用程序集?它在罗斯林本身之外有任何实际用途吗?
此功能的动机确实是构建方案,但它们并非特定于 Roslyn;它们也是你的生成方案。
生成项目时,生成引擎 (MSBuild) 需要确定生成的每个输出相对于其输入是否是最新的。例如,如果不更改任何内容,只是连续运行 build 两次,则第二次不需要调用 C# 编译器:程序集已经正确。
引用程序集允许在更多情况下跳过程序集的编译步骤,因此生成速度更快。我认为一个例子将有助于说明。
假设您有一个包含B.exe
的解决方案,该解决方案依赖于A.dll
。
B 的编译器命令行如下所示
csc.exe /out:B.exe /r:..AbinA.dll Program.cs
它的输入将是
- B的来源 (
Program.cs
) - A的程序集。
如果更改 A 的源代码并生成解决方案,编译器必须为 A 运行,从而生成新的A.dll
。然后,由于A.dll
是 B 编译的输入,因此 B 也必须重新编译。
对 A 使用参照程序集会稍微改变这一点
csc.exe /out:B.exe /r:..AbinrefA.dll Program.cs
A 的输入现在是它的引用程序集,而不是它的实现/正常程序集。
由于引用程序集小于完整程序集,因此这本身对生成时间的影响很小。但这还不足以证明此功能的合理性。重要的是,编译器只关心传入引用的公共 API 图面。如果程序集的内部实现详细信息已更改,则无需重新编译引用它的程序集即可选取新行为。正如@Hans Passant在评论中提到的那样,这就是.NET Framework本身可以提供兼容的性能改进和对未更改的用户代码的错误修复的方式。
引用程序集功能的好处来自为使用它们而完成的 MSBuild 工作。假设您更改了 A 中的内部实现详细信息,但不更改其公共接口。在下一次构建时,
- 编译器必须为 A 运行,因为 A 的源文件已更改。
- 编译器发出
A.dll
(使用更改的实现)和refA.dll
,这与前面的引用程序集相同。 - 由于
refA.dll
与之前的输出相同,因此不会将其复制到 A 的输出文件夹中。 - 当 B 的编译器运行时,它看到它的所有输入都没有更改 - 无论是 B 自己的代码还是 A 的引用程序集,因此编译器不必运行。
- 然后,B 将更新的
A.dll
复制到其输出,并准备好使用新行为运行。
跳过下游编译的效果可能会随着大型解决方案的进行而加剧 - 更改{ProjectName}.Utilities.dll
中的注释不再需要构建所有内容!
许多更改涉及更改公共 API 图面和内部实现,因此此更改不会加快所有生成速度,但会加快许多生成速度。