使用英语阅读

通过


生成 XML API 文档注释

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

此过程为在代码中添加 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 { }

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

XML 注释格式

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

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

    备注

    在代码编辑器中键入///分隔符后,Visual Studio 会自动插入<summary></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 namespace 不能向命名空间添加文档注释,但可以在支持的情况下创建对它们的 cref 引用。
    T 类型 类型是类、接口、结构、枚举或委托。
    F field
    P property 包括索引器或其他索引属性。
    M 方法 包括特殊方法,如构造函数和运算符。
    E event
    ! 错误字符串 其余字符串提供有关错误的信息。 C# 编译器为无法解析的链接生成错误信息。
  • 字符串的第二部分是项的完全限定名称,从命名空间的根目录开始。 项的名称、其封闭类型和命名空间通过句点进行分隔。 如果项本身的名称有句点,则它们将被替换为哈希符号 ('#')。 语法假定没有项直接在其名称中具有哈希符号。 例如,String 构造函数的完全限定名称为“System.String.#ctor”。

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

    • 基类型。 常规类型 (ELEMENT_TYPE_CLASSELEMENT_TYPE_VALUETYPE) 表示为类型的完全限定名称。
    • 内部类型(例如,、ELEMENT_TYPE_I4ELEMENT_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"> 是在 System.Decimal 类中声明的强制转换运算符 public static explicit operator int (decimal value); 的标记。

  • 对于泛型类型,类型名称后跟反引号,然后是指示泛型类型参数数量的数字。 例如:<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# 语言规范 附件。