何时创建和分发 "reference assemblies"?

When to create and distribute "reference assemblies"?

C# 7.1 introduced a few new command line parameters to help create "reference assemblies". By documentation 它输出一个程序集:

have their method bodies replaced with a single throw null body, but include all members except anonymous types.

我找到了 an interesting note that it is more stable on changes:

That means it changes less often than the full assembly--many common development activities don't change the interface, only the implementation. That means that incremental builds can be much faster- ...

还有 it is probably necessary for roslyn itself ..

We will be introducing a second concept, which is "reference assemblies" (also called skeleton assemblies). [---] They will be used for build scenarios.

.. 无论那些 "build scenarios" 是给 Roslyn 的。

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

它的用处似乎比较小众。

所以,我想知道通用程序集生产者方面的事情 - 什么时候应该明确考虑使用那些新的编译器标志来创建参考程序集?它在 Roslyn 本身之外有任何实际用途吗?

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

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

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

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

B 的编译器命令行类似于

csc.exe /out:B.exe /r:..\A\bin\A.dll Program.cs

它的输入是

  • 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

A 的输入现在是它的 参考程序集 ,而不是它的 implementation/normal 程序集。

由于参考程序集小于完整程序集,因此它本身对构建时间的影响很小。但这还不足以证明此功能的合理性。重要的是 编译器只关心 passed-in 引用 的 public API 表面。如果程序集的内部实现细节发生了变化,引用它的程序集不需要重新编译来获取新的行为。正如@Hans Passant 在评论中提到的那样,这就是 .NET Framework 本身如何在未更改的用户代码上提供兼容的性能改进和错误修复。

引用程序集功能的好处来自为使用它们所做的 MSBuild 工作。假设您更改 A 中的内部实现细节但不更改其 public 接口。在下一个版本中,

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

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

许多更改涉及更改 public API 表面和内部实现,因此此更改不会加速所有构建,但它确实加速了许多构建。