XML 文档注释

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 {}

设置 GenerateDocumentationFileDocumentationFile 选项后,编译器将在源代码中找到包含 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 注释创建输出:

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

ID 字符串

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

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

  • 字符串不得包含空白符。

  • 该字符串的第一部分使用单个字符后跟冒号来标识成员类型。 使用下面的成员类型:

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

  • 对于属性和方法,括号内的参数列表如下。 如果没有任何参数,则不会出现括号。 这些参数以逗号分隔。 每个参数的编码直接遵循它在 .NET 签名中的编码方式(请参阅 Microsoft.VisualStudio.CorDebugInterop.CorElementType,了解以下列表中的所有大写元素的定义):

    • 基类型。 常规类型(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 表示为 [lowerbound:size,lowerbound: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:type(signature)”,其中 type 是返回类型,signature 是方法的参数。 如果没有任何自变量,则省略括号。 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() { return 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 nm, 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] { get { return 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) { return 1; }
    }
}

C# 语言规范

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