附录 D. 文档注释

本附录内容丰富。

D.1. 常规

C# 为程序员提供了一种机制,以便使用包含 XML 文本的注释语法来记录其代码。 在源代码文件中,具有特定形式的注释可用于指示工具从这些注释及其前面的源代码元素生成 XML。 使用此语法的注释被称为文档注释。 它们必须紧跟在用户定义的类型(如类、代理或接口)或成员(如字段、事件、属性或方法)之前。 XML 生成工具被称为文档生成器。 (此生成器可能为(但不一定是)C# 编译器自身。)文档生成器生成的输出被称为文档文件。 文档文件被用作文档查看器的输入,而该工具旨在生成类型信息及其关联文档的某种可视化显示。

符合要求的 C# 编译器无需检查文档注释的语法,因为此类评论仅为普通评论。 但是,允许符合要求的编译器执行此类检查。

此规范建议在文档注释中使用一组标准标记,但不要求使用这些标记,且只要遵循格式正确的 XML 规则即可,同时还可按需使用其他标记。 对于面向 CLI 的 C# 实现,它还提供有关文档生成器和文档文件格式的信息。 不提供有关文档查看器的信息。

D.2. 简介

具有特定形式的注释可用于指示工具从这些注释及其前面的源代码元素生成 XML。 此类注释是以三个斜杠 (///) 开头的Single_Line_Comment (§6.3.3),或以斜杠和两个星号 (/**) 开头的 Delimited_Comment (§6.3.3)。 它们必须紧跟在用户定义的类型或它们批注的成员之前。 属性部分 (§22.3) 属性部分被视为声明的一部分,因此文档注释必须位于应用于类型或成员的属性之前。

出于说明目的,文档注释的格式为两个语法规则,如下所示:Single_Line_Doc_CommentDelimited_Doc_Comment。 但是,这些规则不是 C# 语法的一部分,而是分别代表 Single_Line_CommentDelimited_Comment 语法分析器规则的特定格式。

语法:

Single_Line_Doc_Comment
    : '///' Input_Character*
    ;
   
Delimited_Doc_Comment
    : '/**' Delimited_Comment_Section* ASTERISK+ '/'
    ;

Single_Line_Doc_Comment 中,如果每个 Single_Line_Doc_Comments(与当前 Single_Line_Doc_Comment 相邻)中均有一个空格字符跟在 /// 字符后面,则该空格字符不会包含在 XML 输出中。

Delimited_Doc_Comment 中,如果第二行的第一个非空格字符是星号且可选的空格字符和星号字符会在 Delimited_Doc_Comment 内的每一行的开头重复出现,则重复模式的字符不会包含在 XML 输出中。 此模式可能包括星号字符之后或之前的空格字符。

示例:

/// <summary>
/// Class <c>Point</c> models a point in a two-dimensional plane.
/// </summary>
public class Point
{
    /// <summary>
    /// Method <c>Draw</c> renders the point.
    /// </summary>
    void Draw() {...}
}

文档注释中的文本必须根据 XML 规则 (http://www.w3.org/TR/REC-xml) 正确设置格式。 如果 XML 格式不正确,则会生成警告,且文档文件将包含一条表示遇到错误的注释。

虽然开发人员可随意创建自己的标记集,但 §D.3 中定义了一组推荐的标记。 部分建议标记具有特殊含义:

  • <param> 标记用于描述参数。 如果使用此类标记,文档生成器则须确保指定的参数存在,且文档注释描述了所有参数。 如果此类验证失败,文档生成器则会发出警告。

  • cref 属性可以附加到任何标记,以提供对代码元素的引用。 文档生成器必须确保此代码元素存在。 如果验证失败,文档生成器则会发出警告。 查找 cref 属性中描述的名称时,文档生成器必须根据源代码中出现的 using 语句来考虑命名空间可见性。 对于泛型代码元素,无法使用常规泛型语法(例如“List<T>”),因为它会生成无效的 XML。 可使用大括号代替括号(例如“List{T}”)或使用 XML 转义语法(例如“List&lt;T&gt;”)。

  • <summary> 标记旨在供文档查看器用于显示有关类型或成员的其他信息。

  • <include> 标记包含来自外部 XML 文件的信息。

请特别注意,文档文件不提供有关该类型和成员的完整信息(例如,它不含任何类型信息)。 要获取有关类型或成员的此类信息,必须将文档文件与对相关类型或成员的反射一起使用。

分部类型或分部方法可在多个部分进行声明,且每个部分均可位于一个或多个编译单元中,同时每个部分均可有一个或多个文档注释。 分部方法通常具有“定义分部声明”和“实现分部声明”。

对于分部类型,直接应用于该类型的文档注释(如果有)均会从其每个部分以某种未指定的顺序全部写入文档文件。

对于分部方法:

  • 如果定义分部声明没有相应的实现分部声明,则该定义分部声明中的所有文档注释均会被忽略(因为该声明将被删除)。
  • 否则,如果实现分部声明具有任何文档注释,则会将其写入文档文件,并忽略定义分部声明中的所有文档注释。
  • 否则,定义分部声明中的所有文档注释均会写入文档文件。

D.3.1. 常规

文档生成器必须根据 XML 规则来接受并处理有效的所有标记。 下列标记提供用户文档中的常用功能。 (当然,其他标记也可能存在。)

标记 参考 用途
<c> §D.3.2 以类似代码的字体设置文本
<code> §D.3.3 设置一行或多行源代码或程序输出
<example> §D.3.4 指示示例
<exception> §D.3.5 标识某一方法可能引发的异常
<include> §D.3.6 包含来自外部文件的 XML
<list> §D.3.7 创建列表或表
<para> §D.3.8 允许将结构添加到文本中
<param> §D.3.9 描述方法或构造函数的参数
<paramref> §D.3.10 确定某一单词为参数名称
<permission> §D.3.11 记录成员的安全可访问性
<remarks> §D.3.12 描述有关类型的其他信息
<returns> §D.3.13 描述某一方法的返回值
<see> §D.3.14 指定链接
<seealso> §D.3.15 生成另请参阅条目
<summary> §D.3.16 描述类型或类型的成员
<typeparam> §D.3.17 描述泛型类型或方法的类型参数
<typeparamref> §D.3.18 确定某一单词为类型参数名称
<value> §D.3.19 描述属性

D.3.2 <c>

此标记提供了一种机制,可用于指示描述中的文本片段应使用特殊字体进行设置,例如用于代码块的字体。 对于实际代码行,请使用 <code> (§D.3.3)。

语法:

<c> text</c>

示例:

/// <summary>
/// Class <c>Point</c> models a point in a two-dimensional plane.
/// </summary>
public class Point
{
}

D.3.3. <code>

此标记可用于以某种特殊字体设置一行或多行源代码或程序输出。 对于叙述中的小代码片段,请使用 <c> (§D.3.2)。

语法:

<code> source code or program output</code>

示例:

public class Point
{
    /// <summary>
    /// This method changes the point's ___location by the given x- and y-offsets.
    /// <example>
    /// For example:
    /// <code>
    /// Point p = new Point(3,5);
    /// p.Translate(-1,3);
    /// </code>
    /// results in <c>p</c>'s having the value (2,8).
    /// </example>
    /// </summary>
    public void Translate(int dx, int dy)
    {
        ...
    }
}

D.3.4. <example>

此标记允许注释中的示例代码指定如何使用某一方法或其他库成员。 通常,这还涉及使用标记 <code> (§D.3.3)。

语法:

<example> description</example>

示例:

有关示例,请参阅 <code> (§D.3.3)。

D.3.5. <exception>

此标记提供了一种用于记录某一方法可能引发的异常。

语法:

<exception cref=" member">description</exception>

其中

  • cref=" member" 是成员的名称。 文档生成器会检查给定成员是否存在,并将 member 转换为文档文件中的规范元素名称。
  • description 是对引发异常的情况的描述。

示例:

class PrimaryFileFormatCorruptException : System.Exception { ... }
class PrimaryFileLockedOpenException : System.Exception { ... }

public class DataBaseOperations
{
    /// <exception cref="PrimaryFileFormatCorruptException">
    /// Thrown when the primary file is corrupted.
    /// </exception>
    /// <exception cref="PrimaryFileLockedOpenException">
    /// Thrown when the primary file is already open.
    /// </exception>
    public static void ReadRecord(int flag)
    {
        if (flag == 1)
        {
            throw new PrimaryFileFormatCorruptException();
        }
        else if (flag == 2)
        {
            throw new PrimaryFileLockedOpenException();
        }
        ...
    }
}

D.3.6. <include>

此标记允许包含位于源代码文件外部的 XML 文档中的信息。 外部文件必须是格式正确的 XML 文档,且 XPath 表达式会应用于该文档以指定要包含该文档中的哪个 XML。 然后,<include> 标记将替换为外部文档中选定的 XML。

语法

<include file=" filename" path="xpath" />

其中

  • file=" filename" 是外部 XML 文件的文件名。 文件名是相对于包含 include 标记的文件来解释的。
  • path=" xpath" 是一个 XPath 表达式,可用于选择外部 XML 文件中的某些 XML。

示例

如果源代码包含如下声明:

/// <include file="docs.xml" path='extradoc/class[@name="IntList"]/*' />
public class IntList { ... }

且外部文件“docs.xml”包含以下内容:

<?xml version="1.0"?>
<extradoc>
    <class name="IntList">
        <summary>
            Contains a list of integers.
        </summary>
    </class>
    <class name="StringList">
        <summary>
            Contains a list of strings.
        </summary>
    </class>
</extradoc>

则会输出相同的文档,就像源代码包含:

/// <summary>
/// Contains a list of integers.
/// </summary>
public class IntList { ... }

D.3.7. <list>

此标记可用于创建项目的列表或表。 它可能包含 <listheader> 块,以定义表或定义列表的标题行。 (定义表时,只需提供标题中的 term 的条目。)

列表中的每个项均使用 <item> 块指定。 创建定义列表时,必须同时指定 termdescription。 但是,对于表格、项目符号列表或编号列表,只需指定 description

语法:

<list type="bullet" | "number" | "table">
    <listheader>
        <term>term</term>
        <description>description</description>
    </listheader>
    <item>
        <term>term</term>
        <description>description</description>
    </item>
    ...
    <item>
        <term>term</term>
        <description>description</description>
    </item>
</list>

其中

  • term 为要定义的术语,而其定义位于 description 中。
  • description 为项目符号或编号列表中的项,或是某一 term 的定义。

示例:

public class MyClass
{
    /// <summary>Here is an example of a bulleted list:
    /// <list type="bullet">
    /// <item>
    /// <description>Item 1.</description>
    /// </item>
    /// <item>
    /// <description>Item 2.</description>
    /// </item>
    /// </list>
    /// </summary>
    public static void Main()
    {
        ...
    }
}

D.3.8. <para>

此标记用于其他标记内,例如 <summary> (§D.3.16) 或 <returns> (§D.3.13),并允许将结构添加到文本中。

语法:

<para> content</para>

其中

  • content 是段落的文本。

示例:

public class Point
{
    /// <summary>This is the entry point of the Point class testing program.
    /// <para>
    /// This program tests each method and operator, and
    /// is intended to be run after any non-trivial maintenance has
    /// been performed on the Point class.
    /// </para>
    /// </summary>
    public static void Main() 
    {
        ...
    }
}

D.3.9. <param>

此标记可用于描述方法、构造函数或索引器的参数。

语法:

<param name=" name">description</param>

其中

  • name 是参数的名称。
  • description 是参数的描述。

示例:

public class Point
{
    /// <summary>
    /// This method changes the point's ___location to
    /// the given coordinates.
    /// </summary>
    /// <param name="xPosition">the new x-coordinate.</param>
    /// <param name="yPosition">the new y-coordinate.</param>
    public void Move(int xPosition, int yPosition)
    {
        ...
    }
}

D.3.10. <paramref>

此标记可用于指示某一单词为参数。 可处理文档文件,从而以某种不同的方式设置此参数的格式。

语法:

<paramref name=" name"/>

其中

  • name 是参数的名称。

示例:

public class Point
{
    /// <summary>This constructor initializes the new Point to
    /// (<paramref name="xPosition"/>,<paramref name="yPosition"/>).
    /// </summary>
    /// <param name="xPosition">the new Point's x-coordinate.</param>
    /// <param name="yPosition">the new Point's y-coordinate.</param>
    public Point(int xPosition, int yPosition)
    {
        ...
    }
}

D.3.11. <permission>

此标记允许记录成员的安全可访问性。

语法:

<permission cref=" member">description</permission>

其中

  • member 是成员的名称。 文档生成器会检查给定代码元素是否存在,并将 member 转换为文档文件中的规范元素名称。
  • description 是对此成员的访问的描述。

示例:

public class MyClass
{
    /// <permission cref="System.Security.PermissionSet">
    /// Everyone can access this method.
    /// </permission>
    public static void Test()
    {
        ...
    }
}

D.3.12. <remarks>

此标记可用于指定有关类型的额外信息。 使用 <summary> (§D.3.16) 可描述类型自身和类型的成员。

语法:

<remarks> description</remarks>

其中

  • description 是注释的文本。

示例:

/// <summary>
/// Class <c>Point</c> models a point in a two-dimensional plane.
/// </summary>
/// <remarks>
/// Uses polar coordinates
/// </remarks>
public class Point
{
    ...
}

D.3.13. <returns>

此标记可用于描述某一方法的返回值。

语法:

<returns> description</returns>

其中

  • description 是返回值的描述。

示例:

public class Point
{
    /// <summary>
    /// Report a point's ___location as a string.
    /// </summary>
    /// <returns>
    /// A string representing a point's ___location, in the form (x,y),
    /// without any leading, trailing, or embedded whitespace.
    /// </returns>
    public override string ToString() => $"({X},{Y})";
    public int X { get; set; }
    public int Y { get; set; }
}

D.3.14. <see>

此标记允许在文本中指定链接。 使用 <seealso> (§D.3.15) 可指示会出现在 See Also 子句中的文本。

语法:

<see cref=" member" href="url" langword="keyword" />

其中

  • member 是成员的名称。 文档生成器会检查给定代码元素是否存在,并将 member 更改为所生成文档文件中的元素名称。
  • url 是对外部源的引用。
  • langword 是一个会以某种方式突出显示的单词。

示例:

public class Point
{
    /// <summary>
    /// This method changes the point's ___location to
    /// the given coordinates. <see cref="Translate"/>
    /// </summary>
    public void Move(int xPosition, int yPosition)
    {
        ...
    }
    /// <summary>This method changes the point's ___location by
    /// the given x- and y-offsets. <see cref="Move"/>
    /// </summary>
    public void Translate(int dx, int dy)
    {
        ...
    }
}

D.3.15. <seealso>

此标记允许为 See Also 子句生成条目。 使用 <see> (§D.3.14) 可指定来自文本内部的链接。

语法:

<seealso cref=" member" href="url" />

其中

  • member 是成员的名称。 文档生成器会检查给定代码元素是否存在,并将 member 更改为所生成文档文件中的元素名称。
  • url 是对外部源的引用。

示例:

public class Point
{
    /// <summary>
    /// This method determines whether two Points have the same ___location.
    /// </summary>
    /// <seealso cref="operator=="/>
    /// <seealso cref="operator!="/>
    public override bool Equals(object o)
    {
        ...
    }
}

D.3.16. <summary>

此标记可用于描述类型或类型的成员。 使用 <remarks> (§D.3.12) 可指定有关类型或成员的额外信息。

语法:

<summary> description</summary>

其中

  • description 是类型或成员的摘要。

示例:

public class Point
{

    /// <summary>
    /// This constructor initializes the new Point to
    /// (<paramref name="xPosition"/>,<paramref name="yPosition"/>).
    /// </summary>
    public Point(int xPosition, int yPosition) 
    {
        ...
    }

    /// <summary>This constructor initializes the new Point to (0,0).</summary>
    public Point() : this(0, 0)
    {
    }
}

D.3.17. <typeparam>

此标记可用于描述泛型类型或方法的类型参数。

语法:

<typeparam name=" name">description</typeparam>

其中

  • name 是类型参数的名称。
  • description 是类型参数的描述。

示例:

/// <summary>A generic list class.</summary>
/// <typeparam name="T">The type stored by the list.</typeparam>
public class MyList<T>
{
   ...
}

D.3.18. <typeparamref>

此标记可用于指示某一单词为类型参数。 可处理文档文件,从而以某种不同的方式设置此类型参数的格式。

语法:

<typeparamref name=" name"/>

其中

  • name 是类型参数的名称。

示例:

public class MyClass
{
    /// <summary>
    /// This method fetches data and returns a list of
    /// <typeparamref name="T"/>.
    /// </summary>
    /// <param name="query">query to execute</param>
    public List<T> FetchData<T>(string query)
    {
        ...
    }
}

D.3.19. <value>

此标记允许描述属性。

语法:

<value> property description</value>

其中

  • property description 是属性的描述。

示例:

public class Point
{
    /// <value>Property <c>X</c> represents the point's x-coordinate.</value>
    public int X { get; set; }
}

D.4. 处理文档文件

D.4.1. 常规

以下信息适用于面向 CLI 的 C# 实现。

文档生成器会为源代码中标记有文档注释的每个元素生成一个 ID 字符串。 此 ID 字符串可唯一标识源元素。 文档查看器可使用 ID 字符串来标识文档所应用于的相应项目。

文档文件不是源代码的分层表示形式,而是一个平面列表,其中包含为每个元素生成的 ID 字符串。

D.4.2. ID 字符串格式

文档生成器在生成 ID 字符串时会遵循以下规则:

  • 字符串不得包含空格。

  • 该字符串的第一部分可通过单个字符后跟一个冒号来标识被记录的成员类型。 定义了以下类型的成员:

    字符 描述
    E 事件
    F 字段
    M 方法(包括构造函数、终结器和运算符)
    命名空间
    P 属性(包括索引器)
    T 类型(例如类、代理、枚举、接口和结构)
    ! 错误字符串;字符串的其余部分提供了有关此错误的信息。 例如,文档生成器会为无法解析的链接生成错误信息。
  • 该字符串的第二部分是元素的完全限定名称,并从命名空间的根开始。 用句点分隔元素名称、其封闭类型和命名空间。 如果项目名称本身包含句点,会将其替换为 # (U+0023) 字符。 (假定任何元素的名称中都没有此字符。)当成员显式实现泛型接口的成员时,通过将它们周围的“<”和“>”替换为“{”与“}”字符来编码的完全限定名称中的类型参数。

  • 对于带参数的方法和属性,其后会后跟括在括号中的参数列表。 对于没有参数的方法和属性,括号会被省略。 确保自变量之间用逗号分隔。 每个参数的编码与 CLI 签名相同,如下所示:

    • 参数由其文档名称来表示,而该名称基于其完全限定名称并修改如下:
      • 表示泛型类型的参数包含一个追加的“'”字符,且后跟类型参数的数量
      • 具有inoutref 修饰符的参数在其类型名称后面带有 @。 通过 value 或通过 params 传递的参数没有特殊表示法。
      • 作为数组的参数会表示为 [lowerbound:size,, lowerbound:size],其中逗号的数量为排名减 1,且每个维度的下限和大小(如果已知)均以十进制表示。 如果未指定下限或大小,则将其省略。 如果省略某个特定维度的下限和大小,也会省略“:”。 交错数组由每个级别一个“[]”来表示。 当下限为 0(默认值)时,一维数组会省略下限 (§17.1)。
      • 指针类型不是 void 的参数会在类型名称后面使用 * 来表示。 void 指针会使用 System.Void 的类型名称来表示。
      • 引用对类型所定义的泛型类型参数的参数会使用“`”字符后跟类型参数的从零开始的索引来进行编码。
      • 使用方法中定义的泛型类型参数的参数会使用双反引号 “``”而不是用于类型的“`”。
      • 引用构造的泛型类型的参数会使用泛型类型进行编码,且依次后跟“{”、以逗号分隔的类型参数列表以及“}”。

D.4.3. ID 字符串示例

以下示例分别显示了一段 C# 代码,以及从每个能包含文档注释的源元素所生成的 ID 字符串:

类型使用其完全限定名称来表示,并以通用信息进行扩充:

enum Color { Red, Blue, Green }

namespace Acme
{
    interface IProcess { ... }

    struct ValueType { ... }

    class Widget : IProcess
    {
        public class NestedClass { ... }
        public interface IMenuItem { ... }
        public delegate void Del(int i);
        public enum Direction { North, South, East, West }
    }

    class MyList<T>
    {
        class Helper<U,V> { ... }
    }
}

ID:

"T:Color"
"T:Acme.IProcess"
"T:Acme.ValueType"
"T:Acme.Widget"
"T:Acme.Widget.NestedClass"
"T:Acme.Widget.IMenuItem"
"T:Acme.Widget.Del"
"T:Acme.Widget.Direction"
"T:Acme.MyList`1"
"T:Acme.MyList`1.Helper`2"

字段由其完全限定名称来表示。

namespace Acme
{
    struct ValueType
    {
        private int total;
    }

    class Widget : IProcess
    {
        public class NestedClass
        {
            private int value;
        }

        private string message;
        private static Color defaultColor;
        private const double PI = 3.14159;
        protected readonly double monthlyAverage;
        private long[] array1;
        private Widget[,] array2;
        private unsafe int *pCount;
        private unsafe float **ppValues;
    }
}

ID:

"F:Acme.ValueType.total"
"F:Acme.Widget.NestedClass.value"
"F:Acme.Widget.message"
"F:Acme.Widget.defaultColor"
"F:Acme.Widget.PI"
"F:Acme.Widget.monthlyAverage"
"F:Acme.Widget.array1"
"F:Acme.Widget.array2"
"F:Acme.Widget.pCount"
"F:Acme.Widget.ppValues"

构造函数

namespace Acme
{
    class Widget : IProcess
    {
        static Widget() { ... }
        public Widget() { ... }
        public Widget(string s) { ... }
    }
}

ID:

"M:Acme.Widget.#cctor"
"M:Acme.Widget.#ctor"
"M:Acme.Widget.#ctor(System.String)"

终结器

namespace Acme
{
    class Widget : IProcess
    {
        ~Widget() { ... }
    }
}

ID:

"M:Acme.Widget.Finalize"

方法

namespace Acme
{
    struct ValueType
    {
        public void M(int i) { ... }
    }

    class Widget : IProcess
    {
        public class NestedClass
        {
            public void M(int i) { ... }
        }

        public static void M0() { ... }
        public void M1(char c, out float f, ref ValueType v, in int i) { ... }
        public void M2(short[] x1, int[,] x2, long[][] x3) { ... }
        public void M3(long[][] x3, Widget[][,,] x4) { ... }
        public unsafe void M4(char *pc, Color **pf) { ... }
        public unsafe void M5(void *pv, double *[][,] pd) { ... }
        public void M6(int i, params object[] args) { ... }
    }

    class MyList<T>
    {
        public void Test(T t) { ... }
    }

    class UseList
    {
        public void Process(MyList<int> list) { ... }
        public MyList<T> GetValues<T>(T value) { ... } 
    }
}

ID:

"M:Acme.ValueType.M(System.Int32)"
"M:Acme.Widget.NestedClass.M(System.Int32)"
"M:Acme.Widget.M0"
"M:Acme.Widget.M1(System.Char,System.Single@,Acme.ValueType@,System.Int32@)"
"M:Acme.Widget.M2(System.Int16[],System.Int32[0:,0:],System.Int64[][])"
"M:Acme.Widget.M3(System.Int64[][],Acme.Widget[0:,0:,0:][])"
"M:Acme.Widget.M4(System.Char*,Color**)"
"M:Acme.Widget.M5(System.Void*,System.Double*[0:,0:][])"
"M:Acme.Widget.M6(System.Int32,System.Object[])"
"M:Acme.MyList`1.Test(`0)"
"M:Acme.UseList.Process(Acme.MyList{System.Int32})"
"M:Acme.UseList.GetValues``1(``0)"

属性和索引器

namespace Acme
{
    class Widget : IProcess
    {
        public int Width { get { ... } set { ... } }
        public int this[int i] { get { ... } set { ... } }
        public int this[string s, int i] { get { ... } set { ... } }
    }
}

ID:

"P:Acme.Widget.Width"
"P:Acme.Widget.Item(System.Int32)"
"P:Acme.Widget.Item(System.String,System.Int32)"

事件

namespace Acme
{
    class Widget : IProcess
    {
        public event Del AnEvent;
    }
}

ID:

"E:Acme.Widget.AnEvent"

一元运算符

namespace Acme
{
    class Widget : IProcess
    {
        public static Widget operator+(Widget x) { ... }
    }
}

ID:

"M:Acme.Widget.op_UnaryPlus(Acme.Widget)"

所用的完整一元运算符函数名称如下:op_UnaryPlusop_UnaryNegationop_LogicalNotop_OnesComplementop_Incrementop_Decrementop_Trueop_False

二元运算符

namespace Acme
{
    class Widget : IProcess
    {
        public static Widget operator+(Widget x1, Widget x2) { ... }
    }
}

ID:

"M:Acme.Widget.op_Addition(Acme.Widget,Acme.Widget)"

所用的完整二元运算符函数名称如下:op_Additionop_Subtractionop_Multiplyop_Divisionop_Modulusop_BitwiseAndop_BitwiseOrop_ExclusiveOrop_LeftShiftop_RightShiftop_Equalityop_Inequalityop_LessThanop_LessThanOrEqualop_GreaterThanop_GreaterThanOrEqual

转换运算符有一个尾部的“~”,且后跟返回类型。 当转换运算符的源或目标为泛型类型时,“<”与“">”字符将分别替换为“{”与“}”字符。

namespace Acme
{
    class Widget : IProcess
    {
        public static explicit operator int(Widget x) { ... }
        public static implicit operator long(Widget x) { ... }
    }
}

ID:

"M:Acme.Widget.op_Explicit(Acme.Widget)~System.Int32"
"M:Acme.Widget.op_Implicit(Acme.Widget)~System.Int64"

D.5. 示例

D.5.1. C# 源代码

以下示例展示了 Point 类的源代码:

namespace Graphics
{
    /// <summary>
    /// Class <c>Point</c> models a point in a two-dimensional plane.
    /// </summary>
    public class Point
    {
        /// <value>
        /// Property <c>X</c> represents the point's x-coordinate.
        /// </value>
        public int X { get; set; }
        
        /// <value>
        /// Property <c>Y</c> represents the point's y-coordinate.
        /// </value>
        public int Y { get; set; }
        
        /// <summary>
        /// This constructor initializes the new Point to (0,0).
        /// </summary>
        public Point() : this(0, 0) {}
        
        /// <summary>
        /// This constructor initializes the new Point to
        /// (<paramref name="xPosition"/>,<paramref name="yPosition"/>).
        /// </summary>
        /// <param name="xPosition">The new Point's x-coordinate.</param>
        /// <param name="yPosition">The new Point's y-coordinate.</param>
        public Point(int xPosition, int yPosition) 
        {
            X = xPosition;
            Y = yPosition;
        }
        
        /// <summary>
        /// This method changes the point's ___location to
        /// the given coordinates. <see cref="Translate"/>
        /// </summary>
        /// <param name="xPosition">The new x-coordinate.</param>
        /// <param name="yPosition">The new y-coordinate.</param>
        public void Move(int xPosition, int yPosition) 
        {
            X = xPosition;
            Y = yPosition;
        }
        
        /// <summary>
        /// This method changes the point's ___location by
        /// the given x- and y-offsets.
        /// <example>For example:
        /// <code>
        /// Point p = new Point(3, 5);
        /// p.Translate(-1, 3);
        /// </code>
        /// results in <c>p</c>'s having the value (2, 8).
        /// <see cref="Move"/>
        /// </example>
        /// </summary>
        /// <param name="dx">The relative x-offset.</param>
        /// <param name="dy">The relative y-offset.</param>
        public void Translate(int dx, int dy)
        {
            X += dx;
            Y += dy;
        }
        
        /// <summary>
        /// This method determines whether two Points have the same ___location.
        /// </summary>
        /// <param name="o">
        /// The object to be compared to the current object.
        /// </param>
        /// <returns>
        /// True if the Points have the same ___location and they have
        /// the exact same type; otherwise, false.
        /// </returns>
        /// <seealso cref="operator=="/>
        /// <seealso cref="operator!="/>
        public override bool Equals(object o)
        {
            if (o == null)
            {
                return false;
            }
            if ((object)this == o) 
            {
                return true;
            }
            if (GetType() == o.GetType()) 
            {
                Point p = (Point)o;
                return (X == p.X) && (Y == p.Y);
            }
            return false;
        }

        /// <summary>
        /// This method returns a Point's hashcode.
        /// </summary>
        /// <returns>
        /// The int hashcode.
        /// </returns>
        public override int GetHashCode()
        {
            return X + (Y >> 4);    // a crude version
        }
        
        /// <summary>Report a point's ___location as a string.</summary>
        /// <returns>
        /// A string representing a point's ___location, in the form (x,y),
        /// without any leading, training, or embedded whitespace.
        /// </returns>
        public override string ToString() => $"({X},{Y})";
        
        /// <summary>
        /// This operator determines whether two Points have the same ___location.
        /// </summary>
        /// <param name="p1">The first Point to be compared.</param>
        /// <param name="p2">The second Point to be compared.</param>
        /// <returns>
        /// True if the Points have the same ___location and they have
        /// the exact same type; otherwise, false.
        /// </returns>
        /// <seealso cref="Equals"/>
        /// <seealso cref="operator!="/>
        public static bool operator==(Point p1, Point p2)
        {
            if ((object)p1 == null || (object)p2 == null)
            {
                return false;
            }
            if (p1.GetType() == p2.GetType())
            {
                return (p1.X == p2.X) && (p1.Y == p2.Y);
            }
            return false;
        }
        
        /// <summary>
        /// This operator determines whether two Points have the same ___location.
        /// </summary>
        /// <param name="p1">The first Point to be compared.</param>
        /// <param name="p2">The second Point to be compared.</param>
        /// <returns>
        /// True if the Points do not have the same ___location and the
        /// exact same type; otherwise, false.
        /// </returns>
        /// <seealso cref="Equals"/>
        /// <seealso cref="operator=="/>
        public static bool operator!=(Point p1, Point p2) => !(p1 == p2);
    }
}

D.5.2. 生成的 XML

此为一个文档生成器在给定 Point 类的源代码的情况下所生成的输出,如上所示:

<?xml version="1.0"?>
<doc>
  <assembly>
    <name>Point</name>
  </assembly>
  <members>
    <member name="T:Graphics.Point">
    <summary>Class <c>Point</c> models a point in a two-dimensional
    plane.
    </summary>
    </member>
    <member name="M:Graphics.Point.#ctor">
      <summary>This constructor initializes the new Point to (0, 0).</summary>
    </member>
    <member name="M:Graphics.Point.#ctor(System.Int32,System.Int32)">
      <summary>
        This constructor initializes the new Point to
        (<paramref name="xPosition"/>,<paramref name="yPosition"/>).
      </summary>
      <param name="xPosition">The new Point's x-coordinate.</param>
      <param name="yPosition">The new Point's y-coordinate.</param>
    </member>
    <member name="M:Graphics.Point.Move(System.Int32,System.Int32)">
      <summary>
        This method changes the point's ___location to
        the given coordinates.
        <see cref="M:Graphics.Point.Translate(System.Int32,System.Int32)"/>
      </summary>
      <param name="xPosition">The new x-coordinate.</param>
      <param name="yPosition">The new y-coordinate.</param>
      </member>
    <member name="M:Graphics.Point.Translate(System.Int32,System.Int32)">
      <summary>
        This method changes the point's ___location by
        the given x- and y-offsets.
        <example>For example:
        <code>
        Point p = new Point(3,5);
        p.Translate(-1,3);
        </code>
        results in <c>p</c>'s having the value (2,8).
        </example>
        <see cref="M:Graphics.Point.Move(System.Int32,System.Int32)"/>
      </summary>
      <param name="dx">The relative x-offset.</param>
      <param name="dy">The relative y-offset.</param>
    </member>
    <member name="M:Graphics.Point.Equals(System.Object)">
      <summary>
        This method determines whether two Points have the same ___location.
      </summary>
      <param name="o">
        The object to be compared to the current object.
      </param>
      <returns>
        True if the Points have the same ___location and they have
        the exact same type; otherwise, false.
      </returns>
      <seealso 
        cref="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)" />
      <seealso 
        cref="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"/>
    </member>
     <member name="M:Graphics.Point.ToString">
      <summary>
        Report a point's ___location as a string.
      </summary>
      <returns>
        A string representing a point's ___location, in the form (x,y),
        without any leading, training, or embedded whitespace.
      </returns>
     </member>
    <member name="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)">
      <summary>
        This operator determines whether two Points have the same ___location.
      </summary>
      <param name="p1">The first Point to be compared.</param>
      <param name="p2">The second Point to be compared.</param>
      <returns>
        True if the Points have the same ___location and they have
        the exact same type; otherwise, false.
      </returns>
      <seealso cref="M:Graphics.Point.Equals(System.Object)"/>
      <seealso
        cref="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)"/>
    </member>
    <member
        name="M:Graphics.Point.op_Inequality(Graphics.Point,Graphics.Point)">
      <summary>
        This operator determines whether two Points have the same ___location.
      </summary>
      <param name="p1">The first Point to be compared.</param>
      <param name="p2">The second Point to be compared.</param>
      <returns>
        True if the Points do not have the same ___location and the
        exact same type; otherwise, false.
      </returns>
      <seealso cref="M:Graphics.Point.Equals(System.Object)"/>
      <seealso
        cref="M:Graphics.Point.op_Equality(Graphics.Point,Graphics.Point)"/>
      </member>
      <member name="M:Graphics.Point.Main">
        <summary>
          This is the entry point of the Point class testing program.
          <para>
            This program tests each method and operator, and
            is intended to be run after any non-trivial maintenance has
            been performed on the Point class.
          </para>
        </summary>
      </member>
      <member name="P:Graphics.Point.X">
        <value>
          Property <c>X</c> represents the point's x-coordinate.
        </value>
      </member>
      <member name="P:Graphics.Point.Y">
        <value>
          Property <c>Y</c> represents the point's y-coordinate.
        </value>
    </member>
  </members>
</doc>

信息性文本的结尾。