生成 XML API 文档注释

C# 源文件可以包含结构化注释,这些注释为这些文件中定义的类型生成 API 文档。 C# 编译器生成一个 XML 文件,其中包含表示注释和 API 签名的结构化数据。 例如,其他工具可以处理 XML 输出以网页或 PDF 文件的形式创建人工可读文档。

C# 语言参考记录了最近发布的 C# 语言版本。 它还包含即将发布的语言版本公共预览版中功能的初始文档。

本文档标识了在语言的最后三个版本或当前公共预览版中首次引入的任何功能。

小窍门

若要查找 C# 中首次引入功能时,请参阅 有关 C# 语言版本历史记录的文章。

此过程为在代码中添加 API 文档提供了许多优势:

  • C# 编译器将 C# 代码的结构与注释的文本合并到单个 XML 文档中。
  • C# 编译器验证注释是否与相关标记的 API 签名匹配。
  • 处理 XML 文档文件的工具可以定义特定于这些工具的 XML 元素和属性。

Visual Studio 等工具为文档注释中使用的许多常见 XML 元素提供 IntelliSense。

本文介绍以下主题:

  • 文档注释和 XML 文件生成
  • 由 C# 编译器和 Visual Studio 验证的标记
  • 生成的 XML 文件的格式

创建 XML 文档输出

通过编写三斜杠指示的特殊注释字段,为代码创建文档。 注释字段包括描述注释后面的代码块的 XML 元素。 例如:

/// <summary>
/// This class performs an important function.
/// </summary>
public class MyClass { }

设置 GenerateDocumentationFileDocumentationFile 选项。 编译器查找源代码中带有 XML 标记的所有注释字段,并从这些注释创建 XML 文档文件。 启用此选项时,编译器将为项目中声明的任何公开可见成员生成 CS1591 警告,而无需 XML 文档注释。

XML 注释格式

使用 XML 文档注释需要分隔符来指示文档注释的开始和结束位置。 将以下分隔符与 XML 文档标记配合使用:

  • /// 单行分隔符:文档示例和 C# 项目模板使用此表单。 如果空格位于分隔符后面,则 XML 输出中不包含该空格。

    注释

    在代码编辑器中键入<summary>分隔符后,Visual Studio 会自动插入</summary>///和标记并将光标置于这些标记中。 可以在 “选项”对话框中打开或关闭此功能。

  • /** */ 多行分隔符: /** */ 分隔符具有以下格式规则:
    • 在包含 /** 分隔符的行上,如果行的其余部分为空格,则不会为注释处理该行。 如果分隔符后面的 /** 第一个字符为空格,则忽略该空格字符,并处理该行的其余部分。 否则,分隔符后面的 /** 行的整个文本将作为注释的一部分进行处理。

    • 在包含 */ 分隔符的行上,如果直到 */ 分隔符只有空格,则忽略该行。 否则,将 */ 分隔符之前的行的文本作为注释的一部分进行处理。

    • 对于以分隔符开头 /** 的行之后的行,编译器会在每行的开头查找一个通用模式。 该模式可以包含可选的空格和/或星号(*),后跟更多可选空格。 如果编译器在每行开头发现一个不以 /** 分隔符开头且不以 */ 分隔符结尾的通用模式,它会忽略该模式。

    • 以下注释中将被处理的唯一部分是以 <summary> 开头的行。 这三种标记格式生成相同的注释。

      /** <summary>text</summary> */
      
      /**
      <summary>text</summary>
      */
      
      /**
      * <summary>text</summary>
      */
      
    • 编译器标识第二行和第三行开头的“*”的常见模式。 该模式不包括在输出中。

      /**
      * <summary>
      * text </summary>*/
      
    • 编译器在以下注释中找不到常见模式,因为第三行的第二个字符不是星号。 第二行和第三行中的所有文本都作为注释的一部分进行处理。

      /**
      * <summary>
         text </summary>
      */
      
    • 编译器在以下注释中找不到模式,原因有两个。 首先,星号之前的空格数不一致。 其次,第 5 行以制表符开头,这与空格不匹配。 第二行至五行中的所有文本都作为批注的一部分进行处理。

      /**
        * <summary>
        * text
      *  text2
       	*  </summary>
      */
      

若要引用 XML 元素(例如,函数处理要在 XML 文档注释中描述的特定 XML 元素),请使用标准引用机制(&lt;&gt;)。 若要在代码引用 (cref) 元素中引用泛型标识符,请使用转义字符(例如 cref="List&lt;T&gt;")或大括号(cref="List{T}")。 作为特殊情况,编译器将大括号解析为尖括号,以便作者在引用泛型标识符时,文档注释显得不那么繁琐。

注释

如果使用单行 XML 注释分隔符编写注释, ///但不包含任何标记,编译器会将这些注释的文本添加到 XML 输出文件中。 但是,输出不包含 XML 元素,如 <summary>. 使用 XML 注释(包括 Visual Studio IntelliSense)的大多数工具不会阅读这些注释。

接受 XML 文档输入的工具

以下工具从 XML 注释创建输出:

  • DocFXDocFX 是适用于 .NET 的 API 文档生成器,目前支持 C#、Visual Basic 和 F#。 它还允许自定义生成的参考文档。 DocFX 从源代码和 Markdown 文件生成静态 HTML 网站。 此外,DocFX 还提供通过模板自定义网站的布局和样式的灵活性。 还可以创建自定义模板。
  • SandcastleSandcastle 工具 为包含概念页和 API 参考页的托管类库创建帮助文件。 Sandcastle 工具基于命令行,没有 GUI 前端、项目管理功能或自动化生成过程。 Sandcastle 帮助文件生成器提供独立的 GUI 和基于命令行的工具,以自动化方式生成帮助文件。 Visual Studio 集成包也可用于它,以便可以从 Visual Studio 中完全创建和管理帮助项目。
  • DoxygenDoxygen 从一组记录的源文件生成联机文档浏览器(HTML)或脱机参考手册(在 LaTeX 中)。 还支持在 RTF(MS Word)、PostScript、超链接 PDF、压缩 HTML、DocBook 和 Unix 手动页中生成输出。 可以将 Doxygen 配置为从未记录的源文件中提取代码结构。

注释

XML 文档注释不是元数据。 编译器不包含在编译的程序集中,因此无法通过反射访问它们。

ID 字符串

编译器将每个类型或成员写入输出 XML 文件中的元素。 每个元素都有一个唯一的 ID 字符串,用于标识类型或成员。 ID 字符串包括有关运算符、参数、返回值、泛型类型参数、refinout参数的信息。 若要对所有这些潜在元素进行编码,编译器遵循明确定义的规则来生成 ID 字符串。 处理 XML 文件的程序使用 ID 字符串标识文档适用的相应 .NET 元数据或反射项。

编译器在生成 ID 字符串时遵循以下规则:

  • 字符串不包含空格。

  • 该字符串以一个字符和一个标识成员类型的冒号开头。 使用以下成员类型:

    字符 成员类型 注释
    N 命名空间 不能向命名空间添加文档注释,但可以在支持的情况下创建对它们的 cref 引用。
    T 类型 类型是类、接口、结构、枚举或委托。
    F 字段
    P 属性 包括索引器或其他索引属性。
    M 方法 包括特殊方法,如构造函数和运算符。
    E 事件
    ! 错误字符串 其余字符串提供有关错误的信息。 C# 编译器为无法解析的链接生成错误信息。
  • 字符串的第二部分是项的完全限定名称,从命名空间的根目录开始。 项的名称、其封闭类型和命名空间由句点分隔。 如果项的名称具有句点,编译器会将它们替换为哈希符号 ('#')。 语法假定没有项直接在其名称中具有哈希符号。 例如,String 构造函数的完全限定名称为“System.String.#ctor”。

  • 对于属性和方法,括在括号中的参数列表如下。 如果没有参数,则不存在括号。 参数用逗号分隔。 每个参数的编码直接遵循在 .NET 签名中编码的方式(请参阅 Microsoft.VisualStudio.CorDebugInterop.CorElementType 以下列表中所有 caps 元素的定义):

    • 基类型。 常规类型 (ELEMENT_TYPE_CLASSELEMENT_TYPE_VALUETYPE) 表示为类型的完全限定名称。
    • 内部类型(例如ELEMENT_TYPE_I4,、ELEMENT_TYPE_OBJECTELEMENT_TYPE_STRINGELEMENT_TYPE_TYPEDBYREFELEMENT_TYPE_VOID)表示为相应完整类型的完全限定名称。 例如,System.Int32System.TypedReference
    • ELEMENT_TYPE_PTR 的表示形式为:在修改类型之后添加“*”。
    • ELEMENT_TYPE_BYREF 的表示形式为:在修改类型之后添加“@”。
    • ELEMENT_TYPE_CMOD_OPT 的表示形式为:在修改类型之后添加“!”和修饰符类的完全限定名称。
    • ELEMENT_TYPE_SZARRAY 在数组的元素类型之后表示为“[]”。
    • ELEMENT_TYPE_ARRAY 表示为 [下限size下限size],其中逗号数为排名 - 1,并且每个维度的下限和大小(如果已知)以十进制表示。 如果未指定下限和大小,则会省略它们。 如果省略特定维度的下限和大小,则也会省略“:”。 例如,一个下限为 1 且大小未指定的二维数组为 [1:,1:]。
  • 对于仅转换运算符(op_Implicitop_Explicit),方法返回值将编码为 ~ 后跟返回类型。 例如,<member name="M:System.Decimal.op_Explicit(System.Decimal arg)~System.Int32"> 是在 public static explicit operator int (decimal value); 类中声明的强制转换运算符 System.Decimal 的标记。

  • 对于泛型类型,类型名称后跟反引号,然后是指示泛型类型参数数量的数字。 例如:<member name="T:SampleClass`2"> 是定义为 public class SampleClass<T, U> 的类型标记。 对于采用泛型类型作为参数的方法,泛型类型参数被指定为以反杆开头的数字(例如“0,”1)。 每个数字表示类型泛型参数的从零开始的数组表示法。

    • ELEMENT_TYPE_PINNED 的表示形式为:在修改类型之后添加“^”。 C# 编译器永远不会生成此编码。
    • ELEMENT_TYPE_CMOD_REQ 被表示为“|”,并在修改后的类型之后显示修饰符类的完全限定名称。 C# 编译器永远不会生成此编码。
    • ELEMENT_TYPE_GENERICARRAY 在数组的元素类型之后表示为“[?]”。 C# 编译器永远不会生成此编码。
    • ELEMENT_TYPE_FNPTR 表示为“=FUNC:typesignature)”,其中 type 返回类型, 签名 是方法的参数。 如果没有参数,则省略括号。 C# 编译器永远不会生成此编码。
    • 不表示下列签名组件,因为这些组件不用于区分重载方法:
      • 调用约定
      • 返回类型
      • ELEMENT_TYPE_SENTINEL

以下示例演示编译器如何为类及其成员生成 ID 字符串:

namespace MyNamespace;

/// <summary>
/// Enter description here for class X.
/// ID string generated is "T:MyNamespace.MyClass".
/// </summary>
public unsafe class MyClass
{
    /// <summary>
    /// Enter description here for the first constructor.
    /// ID string generated is "M:MyNamespace.MyClass.#ctor".
    /// </summary>
    public MyClass() { }

    /// <summary>
    /// Enter description here for the second constructor.
    /// ID string generated is "M:MyNamespace.MyClass.#ctor(System.Int32)".
    /// </summary>
    /// <param name="i">Describe parameter.</param>
    public MyClass(int i) { }

    /// <summary>
    /// Enter description here for field Message.
    /// ID string generated is "F:MyNamespace.MyClass.Message".
    /// </summary>
    public string? Message;

    /// <summary>
    /// Enter description for constant PI.
    /// ID string generated is "F:MyNamespace.MyClass.PI".
    /// </summary>
    public const double PI = 3.14;

    /// <summary>
    /// Enter description for method Func.
    /// ID string generated is "M:MyNamespace.MyClass.Func".
    /// </summary>
    /// <returns>Describe return value.</returns>
    public int Func() => 1;

    /// <summary>
    /// Enter description for method SomeMethod.
    /// ID string generated is "M:MyNamespace.MyClass.SomeMethod(System.String,System.Int32@,System.Void*)".
    /// </summary>
    /// <param name="str">Describe parameter.</param>
    /// <param name="num">Describe parameter.</param>
    /// <param name="ptr">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public int SomeMethod(string str, ref int num, void* ptr) { return 1; }

    /// <summary>
    /// Enter description for method AnotherMethod.
    /// ID string generated is "M:MyNamespace.MyClass.AnotherMethod(System.Int16[],System.Int32[0:,0:])".
    /// </summary>
    /// <param name="array1">Describe parameter.</param>
    /// <param name="array">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public int AnotherMethod(short[] array1, int[,] array) { return 0; }

    /// <summary>
    /// Enter description for operator.
    /// ID string generated is "M:MyNamespace.MyClass.op_Addition(MyNamespace.MyClass,MyNamespace.MyClass)".
    /// </summary>
    /// <param name="first">Describe parameter.</param>
    /// <param name="second">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public static MyClass operator +(MyClass first, MyClass second) { return first; }

    /// <summary>
    /// Enter description for property.
    /// ID string generated is "P:MyNamespace.MyClass.Prop".
    /// </summary>
    public int Prop { get { return 1; } set { } }

    /// <summary>
    /// Enter description for event.
    /// ID string generated is "E:MyNamespace.MyClass.OnHappened".
    /// </summary>
    public event Del? OnHappened;

    /// <summary>
    /// Enter description for index.
    /// ID string generated is "P:MyNamespace.MyClass.Item(System.String)".
    /// </summary>
    /// <param name="str">Describe parameter.</param>
    /// <returns></returns>
    public int this[string s] => 1;

    /// <summary>
    /// Enter description for class Nested.
    /// ID string generated is "T:MyNamespace.MyClass.Nested".
    /// </summary>
    public class Nested { }

    /// <summary>
    /// Enter description for delegate.
    /// ID string generated is "T:MyNamespace.MyClass.Del".
    /// </summary>
    /// <param name="i">Describe parameter.</param>
    public delegate void Del(int i);

    /// <summary>
    /// Enter description for operator.
    /// ID string generated is "M:MyNamespace.MyClass.op_Explicit(MyNamespace.MyClass)~System.Int32".
    /// </summary>
    /// <param name="myParameter">Describe parameter.</param>
    /// <returns>Describe return value.</returns>
    public static explicit operator int(MyClass myParameter) => 1;
}

C# 语言规范

有关详细信息,请参阅有关文档注释的 C# 语言规范 附件。