.NET Runtime 仓库添加新 API 全流程指南:从库选择、GenAPI 参考源更新到 PlatformDocAnalyzer 文档约束
【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime
在 .NET runtime 仓库中向基础类库添加新的公开 API,需要同时满足接口评审(API Review)、参考程序集同步、测试框架适配与 XML 文档规范四条主线。本文基于仓库中 adding-api-guidelines.md 的完整规范展开,并结合 updating-ref-source.md 的参考源更新流程、PlatformDocAnalyzer 源码与 intellisense.targets 构建逻辑,讲清"在哪个库、哪个 TFM 加 API、如何用 GenAPI 更新参考源、如何写测试、三斜线文档放在哪、为什么放错了会被 CI 拦下"的完整闭环。读完后你可以独立完成一次符合本仓库规范的新 API 提交。
一、新增 API 前的三项决策:选库、选 TFM、选版本
1. 确定 API 应放在哪个库
文档给出的第一步是:先提出一个"以哪个库作为暴露面"的提案,并走 API 评审流程(原文链接指向 aka.ms/apireview 评审入口)。评审的意义在于:.NET 的 API 面是跨版本兼容承诺的核心,任何新增公开 API 都必须在进入实现之前被评审确认命名空间归属与 API 形状。
文档还特别强调一个容易踩坑的事实:
API 很可能暴露在一个与实现程序集身份不一致的参考程序集中。
这样做的主要原因有两个:
- 跨平台抽象运行时程序集身份——同一份 API 面可以在不同平台上以不同的实现程序集身份出现,而消费者编译时看到的参考程序集是统一的;
- 允许未来重构实现而不产生兼容性问题——只要参考程序集不变,实现程序集的名称、内部拆分都可以自由演进。
从仓库结构看,这种"实现面/参考面分离"的布局在src/coreclr/System.Private.CoreLib(运行时内部实现)与对外 dlls 的划分中体现得很明显:核心类型在System.Private.CoreLib中实现,而公开契约由对应的参考程序集表达。
2. 确定目标框架(Target Framework)
文档明确规定:
net11.0是当前正在开发的目标框架版本,新 API 应添加到net11.0。
也就是说,在本仓库当前开发线中,新 API 一律落在net11.0这个 TFM 下,而不是往更低版本的 TFM 里加。如果你的 API 需要平台后缀(如net11.0-windows、net11.0-linux),则后续文档放置规则会受 PlatformDocAnalyzer 约束(见第五节)。
3. 确定库版本
文档将"Determine library version"列为决策项之一,具体版本号策略遵循仓库的发布/版本化规范(参见 project-guidelines.md 中的项目级约定)。实操中,版本号的最终裁决同样在 API 评审阶段完成。
二、在仓库中落地:实现、参考源与测试
1. 实现 API 修改
在最合适的库项目中实现你的 API 修改(Implement your API modification in the appropriate library project)。
2. 更新参考程序集(GenAPI 流程)
新公开 API 落地后,必须同步更新 ref 目录中的参考源,详细步骤见 updating-ref-source.md。该文档给出的标准流程是:
在源程序集中实现 API 并构建。注意:新增公开类型时构建可能因 ApiCompat 的程序集校验报
TypeMustExist错误,可临时用dotnet build /p:ApiCompatValidateAssemblies=false绕过这一"死锁";在src 目录下运行 GenAPI 工具生成参考源:
dotnet msbuild /t:GenerateReferenceAssemblySource文档提醒:GenAPI 可能产生大量不相关的 diff,需要手动挑选你真正关心的改动;不推荐完全手写参考源,因为会与 GenAPI 生成结果产生漂移(drift),导致后续更新更难看清、且参考程序集与运行时程序集存在分叉风险。若你已提前手工加了 API,重新执行
GenerateReferenceAssemblySource(从 ref 目录执行)可将其"扶正"为全限定名并按正确排序放置;进入 ref 目录,构建参考程序集;
添加、构建并运行测试。
该文档还覆盖了三类特殊场景,这里一并给出命令:
System.Runtime 及部分依赖 System.Private.CoreLib 的程序集(如 System.Memory 这类 partial facade):从
System.Runtime/src目录运行dotnet build --no-incremental /t:GenerateReferenceAssemblySource,然后过滤掉无关改动(此步其他参考程序集一般不需要);Full Facade 程序集(实现程序集是另一程序集上的"完整 facade"、但参考程序集中定义了类型,如 System.Runtime.Serialization.Json、System.Xml.XDocument):
dotnet msbuild /t:GenerateReferenceAssemblySource /p:GenAPIFollowTypeForwards=true.NETFramework facade 程序集:类型定义在 .NETStandard/.NETCore 中、在 .NETFramework 上只需转发(type forward)到既有位置的,需要手工为 .NETFramework 参考程序集添加 TypeForwards——对每个在 .NETFramework 中已存在的、兼容 .NETStandard 参考程序集中的类型都要加转发;定义在 .NETFramework 参考程序集中的类型应抽成共享源文件。
3. 更新测试
主文档要求:
将新的
TargetFramework加入测试项目的TargetFrameworks列表;针对新 TFM 的新文件,遵循 代码文件命名约定 编写测试代码。该约定(见 project-guidelines.md)的核心规则是:源文件与类一一对应,文件名为
<class>.cs;按配置分叉的类命名为<class>.<BuildSettings>.cs($(TargetOS)/$(TargetFramework)/$(Configuration)/$(Platform)之一,大小写严格一致);按特性分叉的类命名为<class>.<feature>.cs(如.CoreCLR.cs、.Win32.cs);只运行新 TFM 的测试:
dotnet build <Library>.csproj -f <TargetFramework> /t:Test其中
TargetFramework只能从受支持的 TargetFrameworks 中选择。
三、文档规范:三斜线注释与两条工作流
基本规则
- 所有新的公开 API必须以三斜线注释(triple-slash comments)写在符号上方。在 Visual Studio 中输入
///会自动生成注释骨架; - 如果新 API 或其调用的 API 会抛出异常,必须手动用
<exception></exception>元素补充文档——编译器不会替你推断异常契约; - 语言风格遵循 .NET 官方 API 文档写作指南(dotnet-api-docs wiki)。
工作流分叉:取决于UseCompilerGeneratedDocXmlFile属性
后续文档流程取决于该程序集项目文件中是否设置了UseCompilerGeneratedDocXmlFile属性。eng/intellisense.targets 中可以看到该属性默认为true:
<UseCompilerGeneratedDocXmlFile Condition="'$(UseCompilerGeneratedDocXmlFile)' == ''">true</UseCompilerGeneratedDocXmlFile>据此,仓库中存在两种文档"事实来源"(source of truth)工作流:
A. 未设置该属性(或为true,即默认)的库——源码注释即事实来源:
- 本仓库中的源码注释是文档的 source of truth;
- 源码三斜线注释会周期性地(每个 preview)同步到 dotnet-api-docs 仓库;
- 较新引入的库通常走这条流程。
B. 将该属性显式设为false的库——dotnet-api-docs 仓库是事实来源:
- 文档以 dotnet-api-docs 仓库为准。你的改动合入 runtime 仓库后,最终会把文档移植(port)到 dotnet-api-docs 仓库,移植工具位于 api-docs-sync 仓库;
- dotnet-api-docs 侧的改动合入后,文档才会出现在官方 API 文档站点,随后才进入 Visual Studio / Visual Studio Code 的 IntelliSense;
- 三斜线注释只在新 API 首次同步时被搬到 dotnet-api-docs。之后的所有文档更新必须直接在 dotnet-api-docs 仓库进行;
- 之后继续修改本地三斜线注释用于本地开发是可以的,只是不会自动流入官方文档。小型改动可用 AI 辅助移植,大型改动用 api-docs-sync 仓库提供的PortToDocs工具更合适;
- 较老的库通常走这条流程。文档也指出:这类库未来可以通过 api-docs-sync 工具(或 AI agent)把 dotnet-api-docs 中的文档"反向移植"回源码注释,然后移除
UseCompilerGeneratedDocXmlFile属性,从而迁移到更顺畅的 A 类工作流。
从 intellisense.targets 的实现看,当UseCompilerGeneratedDocXmlFile不为true时,构建会从Microsoft.Private.Intellisense包中解析文档团队提供的 XML(DocFileOverride),并在打包时用它替换编译器生成的文档文件(ChangeDocumentationFileForPackaging目标);同时会 NoWarn 掉 CS1591(缺失 XML 注释警告)。若文档团队未提供对应 XML,构建还会发出明确警告,提示移除该属性、改由编译器生成——这与上文"两条工作流"的判定完全对应。
API 用法示例(usage examples)的规范
用法示例放在测试源码中,这样可以在常规测试运行中被验证。位置二选一,取更贴合测试项目结构者:
- 放在
examples子目录;或 - 文件名带
Examples后缀。
最终发布文档中要采用的具体代码,用#region指令标出,从而把[Fact]特性之类的测试样板排除在文档之外。
在文档中引用示例的方式有两种:
在 XML 文档内引用:在
example元素内使用<code lang="cs" source="..." region="..." />元素;在 Markdown 块内引用:改用
code-csharp指令。原文档给出的示例形式为:!code-csharp[]注意原文中这条示例写的是相对文档自身位置的相对路径;按本文的链接规范,等价于仓库内
tests/System.Text.RegularExpressions/FunctionalTests/Regex.Examples.cs中的Matchregion(该示例路径为原规范文档中的写法,具体示例文件以各库测试项目实际布局为准)。
这两种语法的本质相同:按 region 从源文件中抽取指定代码片段,直接内嵌进最终文档,保证文档示例与可运行测试代码同源、不漂移。
四、平台特定库中的文档放置规则
这是本规范中最"工程化"的部分。当库同时面向平台特定 TFM(如net11.0-windows、net11.0-linux)时:
只会选择一个平台的编译器生成 doc XML作为事实来源、打进分发给所有客户的 IntelliSense 包。
这意味着:如果某个公开 API 的 XML 文档注释只写在平台特定的 partial 文件里,那么在其他平台发布的文档中它就是缺失的。为保证跨平台文档一致,规则有三条:
- 文档放在主源文件:每个公开类型应有一个名为
TypeName.cs的主源文件,所有公开 API 的文档(/// <summary>、/// <param>等)都必须写在这个文件里; - partial 文件遵循命名约定:平台或特性特定的 partial 文件必须命名为
TypeName.Something.cs(如Socket.Windows.cs、Socket.Unix.cs)——这与 project-guidelines.md 的<class>.<BuildSettings>.cs约定一脉相承; - 不要在非主 partial 文件中写公开 XML 文档注释:若某公开成员声明在
TypeName.Windows.cs中,其文档应写在TypeName.cs中(用 partial method 声明或<inheritdoc/>承接),而不是写在平台特定文件里。
PlatformDocAnalyzer:规则的自动执法者
上述规则由 PlatformDocAnalyzer(位于eng/analyzers/PlatformDocAnalyzer)强制执行,该分析器自动应用于所有库源码项目。它的激活条件是:构建平台特定的 TFM,且UseCompilerGeneratedDocXmlFile=true。四条诊断如下:
| 诊断 | 说明 |
|---|---|
| PLATDOC001 | 公开类型缺少名为TypeName.cs的源文件 |
| PLATDOC002 | partial 源文件不符合TypeName.Something.cs命名约定 |
| PLATDOC003 | 非主 partial 文件中的公开成员带有 XML 文档,应移到TypeName.cs |
| PLATDOC004 | 某公开 API 的文档与"无平台"(canonical)构建中的文档不一致 |
PLATDOC001–003 是启发式规则,用于引导源码组织;PLATDOC004 是权威性检查。从 PlatformDocAnalyzer.cs 的OnCompilationStart可以看到其工作机制:
- 分析器先读全局分析器配置
build_property.TargetFramework,只有当 TFM 名带平台后缀(含-,如net10.0-windows,见IsPlatformSpecificTfm)时才继续; - 再读
build_property.UseCompilerGeneratedDocXmlFile,必须为true;这两个 MSBuild 属性经 PlatformDocAnalyzer.props 声明的CompilerVisibleProperty暴露给分析器; - 对 PLATDOC004,构建侧会把 canonical TFM(与平台 TFM 共存的那个无平台 TFM,如
net11.0-windows旁边的net11.0)的编译器生成 doc XML 作为带PlatformDocCanonical=true元数据的 AdditionalFile 传入(由 intellisense.targets 完成,其注释明确写着"Platform-specific builds: pass it to PlatformDocAnalyzer as an AdditionalFile"); - 分析器用正则(而非 XML 解析器)抽取
<member name="...">...</member>元素,避免 XML 解析器的规范化处理造成与GetDocumentationCommentXml()输出的假性不匹配(见 ParseDocXml 的注释);再对每个公开类型及其公开成员,将当前构建的文档与 canonical 文档做空白归一化后的逐字节比较(NormalizeDocXml把连续空白折叠为单空格),两者都为空视为一致,任何差异即报 PLATDOC004。
换句话说:如果某平台 TFM 构建中某个 API 的文档与无平台 TFM 构建不一致,几乎可以断定文档被写在了平台特定源文件上——这正是 PLATDOC004 要抓的失误。
分析器只对跨多文件声明的类型做主文件检查(单文件类型不存在文档放置问题),PLATDOC002 还要求非主文件名严格以TypeName.前缀开头且以.cs结尾、且中间必须有内容。它的测试套件位于eng/analyzers/PlatformDocAnalyzer.Tests,可用dotnet test eng/analyzers/PlatformDocAnalyzer.Tests/PlatformDocAnalyzer.Tests.csproj在本地运行(据 eng/analyzers/README.md 说明,这些测试不在主 CI 流水线中,修改分析器时本地运行)。
合理的例外:pragma 抑制
如果某个文件合理地不符合上述约定(例如沿用既定TypeNameAsync.cs模式的Asyncpartial),应使用#pragma warning disable PLATDOCnnn抑制对应诊断,并附一句简短注释说明原因。
五、FAQ:把类型下沉到更低的契约(contract)时怎么办
原文档 FAQ 收录了一个高频问题:
当你要把类型移动(下沉)到更低的 contract 时该怎么办?
答案:
- 必须同时对两个 contract 做版本化(version both contracts at the same time);
- 过渡期内,两个项目之间临时使用项目引用(project references);
- 在你移除了类型的原位置,必须留下 type-forward,以保持向后兼容。
这与第二节中 .NETFramework facade 场景的手工 TypeForwards 要求呼应:type forward 是本仓库维护 API 兼容性的基本手段,凡是"类型搬家",旧坐标上必须留有转发。
六、流程小结与检查清单
把整条链路串起来,一次合规的新 API 提交应满足:
- 决策:确定归属库(参考程序集身份可与实现不同)、确定 TFM(当前开发线为
net11.0)、确定版本,并通过 API 评审; - 实现:在对应库项目实现 API;
- 参考源:按 updating-ref-source.md 用
dotnet msbuild /t:GenerateReferenceAssemblySource(特殊程序集用对应变体命令)更新 ref 目录,避免手写漂移; - 测试:
TargetFrameworks加入新 TFM,文件命名遵循<class>.cs/<class>.<BuildSettings>.cs约定,用dotnet build <Library>.csproj -f <TargetFramework> /t:Test单独验证; - 文档:三斜线注释覆盖全部公开 API 与异常;示例放测试中的
examples/或*Examples.cs、以#region圈出,用<code lang="cs" source="..." region="..."/>或!code-csharp[]引用; - 平台一致性:平台特定 TFM 下文档一律写在
TypeName.cs主文件,接受 PLATDOC001–004 检查,合理例外用#pragma warning disable加注释抑制。
完成以上六步,新增 API 就能以"实现与参考一致、测试可验证、文档跨平台一致"的状态进入仓库的正式流水线。
【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考