C#文档注释规范(转)
C# 提供一种机制,使程序员可以使用含有 XML 文本的特殊注释语法为他们的代码编写文档。在源代码文件中,具有某种格式的注释可用于指导某个工具根据这些注释和它们后面的源代码元素生成 XML。使用这类语法的注释称为文档注释 (documentation comment)。这些注释后面必须紧跟用户定义类型(如类、委托或接口)或者成员(如字段、事件、属性或方法)。XML 生成工具称作文档生成器 (documentation generator)。(此生成器可以但不一定必须是 C# 编译器本身。)由文档生成器产生的输出称为文档文件(documentation file)。文档文件可作为文档查看器 (documentation viewer) 的输入;文档查看器是用于生成类型信息及其关联文档的某种可视化显示的工具。
此规范推荐了一组在文档注释中使用的标记,但是这些标记不是必须使用的,如果需要也可以使用其他标记,只要遵循“符合格式标准的 XML”规则即可。
A.1. 介绍
具有特殊格式的注释可用于指导某个工具根据这些注释和它们后面的源代码元素生成 XML。这类注释是以三个斜杠 (///) 开始的单行注释,或者是以一个斜杠和两个星号 (/**) 开始的分隔注释。这些注释后面必须紧跟它们所注释的用户定义类型(如类、委托或接口)或者成员(如字段、事件、属性或方法)。属性节(第17.2 节)被视为声明的一部分,因此,文档注释必须位于应用到类型或成员的属性之前。
语法:
single-line-doc-comment: /// input-charactersopt delimited-doc-comment: /** delimited-comment-charactersopt */
在 single-line-doc-comment 中,如果当前 single-line-doc-comment 旁边的每个 single-line-doc-comment 上的 /// 字符后跟有 whitespace 字符,则此 whitespace 字符不包括在 XML 输出中。
在 delimited-doc-comment 中,如果第二行上的第一个非 whitespace 字符是一个 asterisk,并且在delimited-doc-comment 内的每行开头都重复同一个由可选 whitespace 字符和 asterisk 字符组成的样式,则该重复出现的样式所含的字符不包括在 XML 输出中。此样式中,可以在 asterisk 字符之前或之后包括 whitespace 字符。
示例:
/// <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不符合标准格式,将生成警告,并且文档文件将包含一条注释,指出遇到错误。
尽管开发人员可自由创建它们自己的标记集,但第 A.2 节定义有建议的标记集。某些建议的标记具有特殊含义:
- <param> 标记用于描述参数。如果使用这样的标记,文档生成器必须验证指定参数是否存在以及文档注释中是否描述了所有参数。如果此验证失败,文档生成器将发出警告。
- cref 属性可以附加到任意标记,以提供对代码元素的参考。文档生成器必须验证此代码元素是否存在。如果验证失败,文档生成器将发出警告。查找在 cref 属性中描述的名称时,文档生成器必须根据源代码中出现的 using 语句来考虑命名空间的可见性。
- <summary> 标记旨在标出可由文档查看器显示的有关类型或成员的额外信息。
- <include> 标记表示应该包含的来自外部 XML 文件的信息。
注意,文档文件并不提供有关类型和成员的完整信息(例如,它不包含任何关于类型的信息)。若要获得有关类型或成员的完整信息,必须协同使用文档文件与对实际涉及的类型或成员的反射调用。
A.2. 建议的标记
文档生成器必须接受和处理任何根据 XML 规则有效的标记。下列标记提供了用户文档中常用的功能。(当然,也可能有其他标记。)
标记 |
章节 |
用途 |
<c> |
A.2.1 |
将文本设置为类似代码的字体 |
<code> |
A.2.2 |
将一行或多行源代码或程序输出设置为某种字体 |
<example> |
A.2.3 |
表示所含的是示例 |
<exception> |
A.2.4 |
标识方法可能引发的异常 |
<include> |
A.2.5 |
包括来自外部文件的 XML |
<list> |
A.2.6 |
创建列表或表 |
<para> |
A.2.7 |
用于将结构添加到文本中 |
<param> |
A.2.8 |
描述方法或构造函数的参数 |
<paramref> |
A.2.9 |
确认某个单词是参数名 |
<permission> |
A.2.10 |
描述成员的安全性和访问权限 |
<summary> |
A.2.11 |
描述一种类型 |
<returns> |
A.2.12 |
描述方法的返回值 |
<see> |
A.2.13 |
指定链接 |
<seealso> |
A.2.14 |
生成“请参见”项 |
<summary> |
A.2.15 |
描述类型的成员 |
<value> |
A.2.16 |
描述属性 |
A.2.1. <c>
此标记提供一种机制以指示用特殊字体(如用于代码块的字体)设置说明中的文本段落。对于实际代码行,请使用 <code>(第 A.2.2 节)。
语法:
<c>text</c>
示例:
/// <summary>Class <c>Point</c> models a point in a two-dimensional /// plane.</summary> public class Point { // ... }
A.2.2. <code>
此标记用于将一行或多行源代码或程序输出设置为某种特殊字体。对于叙述中较小的代码段,请使用<c>(第 A.2.1 节)。
语法:
<code>source code or program output</code>
示例:
/// <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 xor, int yor) { X += xor; Y += yor; }
A.2.3. <example>
此标记用于在注释中插入代码示例,以说明如何使用所关联的方法或其他库成员。通常,此标记是同标记<code>(第 A.2.2 节)一起使用的。
语法:
<example>description</example>
示例:
有关示例,请参见 <code>(第 A.2.2 节)。
A.2.4. <exception>
此标记提供一种方法以说明关联的方法可能引发的异常。
语法:
<exception cref="member">description</exception>
其中
cref="member"
成员的名称。文档生成器检查给定成员是否存在,并将 member 转换为文档文件中的规范元素名称。
description
对引发异常的情况的描述。
示例:
public class DataBaseOperations { /// <exception cref="MasterFileFormatCorruptException"></exception> /// <exception cref="MasterFileLockedOpenException"></exception> public static void ReadRecord(int flag) { if (flag == 1) throw new MasterFileFormatCorruptException(); else if (flag == 2) throw new MasterFileLockedOpenException(); // … } }
A.2.5. <include>
此标记允许包含来自源代码文件外部的 XML 文档的信息。外部文件必须是符合标准格式的 XML 文档,还可以将 XPath 表达式应用于该文档来指定应包含该 XML 文档中的哪些 XML 文本。然后用从外部文档中选定的 XML 来替换 <include>标记。
语法:
<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 integers. </summary> </class> </extradoc> 这样输出的文档就与源代码中包含以下内容时一样: /// <summary> /// Contains a list of integers. /// </summary> public class IntList { … }
A.2.6. <list>
此标记用于创建列表或项目表。它可以包含 <listheader> 块以定义表或定义列表的标头行。(定义表时,仅需要在标头中为 term 提供一个项。)
列表中的每一项都用一个 <item> 块来描述。创建定义列表时,必须同时指定 term 和 description。但是,对于表、项目符号列表或编号列表,仅需要指定 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 () { // ... } }
A.2.7. <para>
此标记用于其他标记内,如 <summary>(第 A.2.11 节)或 <returns>(第 A.2.12 节),用于将结构添加到文本中。
语法:
<para>content</para>
其中
content
段落文本。
示例:
/// <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-trvial maintenance has /// been performed on the Point class.</para></summary> public static void Main() { // ... }
A.2.8. <param>
该标记用于描述方法、构造函数或索引器的参数。
语法:
<param name="name">description</param>
其中
name
参数名。
description
参数的描述。
示例:
/// <summary>This method changes the point's location to /// the given coordinates.</summary> /// <param name="xor">the new x-coordinate.</param> /// <param name="yor">the new y-coordinate.</param> public void Move(int xor, int yor) { X = xor; Y = yor; }
A.2.9. <paramref>
该标记表示某单词是一个参数。这样,生成文档文件后经适当处理,可以用某种独特的方法来格式化该参数。
语法:
<paramref name="name"/>
其中
name
参数名。
示例:
/// <summary>This constructor initializes the new Point to /// (<paramref name="xor"/>,<paramref name="yor"/>).</summary> /// <param name="xor">the new Point's x-coordinate.</param> /// <param name="yor">the new Point's y-coordinate.</param> public Point(int xor, int yor) { X = xor; Y = yor; }
A.2.10. <permission>
该标记用于将成员的安全性和可访问性记入文档。
语法:
<permission cref="member">description</permission>
其中
cref="member"
成员的名称。文档生成器检查给定的代码元素是否存在,并将 member 转换为文档文件中的规范化元素名称。
description
对成员的访问属性的说明。
示例:
/// <permission cref="System.Security.PermissionSet">Everyone can /// access this method.</permission> public static void Test() { // ... }
A.2.11. <summary>
该标记用于指定类型的概述信息。(使用 <summary>(第 A.2.15 节)描述类型的成员。)
语法:
<summary>description</summary>
其中
description
摘要文本。
示例:
/// <summary>Class <c>Point</c> models a point in a /// two-dimensional plane.</summary> public class Point { // ... }
A.2.12. <returns>
该标记用于描述方法的返回值。
语法:
<returns>description</returns>
其中
description
返回值的说明。
示例:
/// <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() { return "(" + X + "," + Y + ")"; }
A.2.13. <see>
该标记用于在文本内指定链接。使用 <seealso>(第 A.2.14 节)指示将在“请参见”部分中出现的
文本。
语法:
<see cref="member"/>
其中
cref="member"
成员的名称。文档生成器检查给定的代码元素是否存在,并将 member 更改为所生成的文档文件中的元素名称。
示例:
/// <summary>This method changes the point's location to /// the given coordinates.</summary> /// <see cref="Translate"/> public void Move(int xor, int yor) { X = xor; Y = yor; } /// <summary>This method changes the point's location by /// the given x- and y-offsets. /// </summary> /// <see cref="Move"/> public void Translate(int xor, int yor) { X += xor; Y += yor; }
A.2.14. <seealso>
该标记用于生成将列入“请参见”部分的项。使用 <see>(第 A.2.13 节)指定来自文本内的链接。
语法:
<seealso cref="member"/>
其中
cref="member"
成员的名称。文档生成器检查给定的代码元素是否存在,并将 member 更改为所生成的文档文件中的元素名称。
示例:
/// <summary>This method determines whether two Points have the same /// location.</summary> /// <seealso cref="operator=="/> /// <seealso cref="operator!="/> public override bool Equals(object o) { // ... }
A.2.15. <summary>
可以用此标记描述类型的成员。使用 <summary>(第 A.2.11 节)描述类型本身。
语法:
<summary>description</summary>
其中
description
关于成员的摘要描述。
示例:
/// <summary>This constructor initializes the new Point to (0,0).</summary> public Point() : this(0,0) { }
A.2.16. <value>
该标记用于描述属性。
语法:
<value>property description</value>
其中
property description
属性的说明。
示例:
/// <value>Property <c>X</c> represents the point's x-coordinate.</value> public int X { get { return x; } set { x = value; } }
A.3. 处理文档文件
文档生成器为源代码中每个附加了“文档注释标记”的代码元素生成一个 ID 字符串。该 ID 字符串唯一地标识源元素。文档查看器利用此 ID 字符串来标识该文档所描述的对应的元数据/反射项。
文档文件不是源代码的层次化表现形式;而是为每个元素生成的 ID 字符串的一维列表。
A.3.1. ID 字符串格式
文档生成器在生成 ID 字符串时遵循下列规则:
- 不在字符串中放置空白。
- 字符串的第一部分通过单个字符后跟一个冒号来标识被标识成员的种类。定义以下几种成员:
字符 |
说明 |
E |
事件 |
F |
字段 |
M |
方法(包括构造函数、析构函数和运算符) |
N |
命名空间 |
P |
属性(包括索引器) |
T |
类型(如类、委托、枚举、接口和结构) |
! |
错误字符串;字符串的其他部分提供有关错误的信息。例如,文档生成器对无法解析的链接生成错误信息。 |
- 字符串的第二部分是元素的完全限定名,从命名空间的根开始。元素的名称、包含着它的类型和命名空间都以句点分隔。如果项名本身含有句点,则将用 # (U+0023) 字符替换。(这里假定所有元素名中都没有“# (U+0023)”字符。)
- 对于带有参数的方法和属性,接着是用括号括起来的参数列表。对于那些不带参数的方法和属性,则省略括号。多个参数以逗号分隔。每个参数的编码都与 CLI 签名相同,如下所示:参数由其完全限定名来表示。例如,int 变成 System.Int32、string 变成 System.String、object 变成System.Object 等。具有 out 或 ref 修饰符的参数在其类型名后跟有 @ 符。对于由值传递或通过params 传递的参数没有特殊表示法。数组参数表示为 [ lowerbound : size , … , lowerbound :size ],其中逗号数量等于秩减去一,而下限和每个维的大小(如果已知)用十进制数表示。如果未指定下限或大小,它将被省略。如果省略了某个特定维的下限及大小,则“:”也将被省略。交错数组由每个级别一个“[]”来表示。指针类型为非 void 的参数用类型名后面跟一个 * 的形式来表示。void 指针用类型名 System.Void 表示。
A.3.2. 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 } } } "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"
- 字段用它们的完全限定名来表示。
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; } } "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) {...} } } "M:Acme.Widget.#cctor" "M:Acme.Widget.#ctor" "M:Acme.Widget.#ctor(System.String)"
- 析构函数。
namespace Acme { class Widget: IProcess { ~Widget() {...} } } "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) {...} 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) {...} } } "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@)" "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[])"
- 属性和索引器。
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 {...} } } } "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; } } "E:Acme.Widget.AnEvent"
- 一元运算符。
namespace Acme { class Widget: IProcess { public static Widget operator+(Widget x) {...} } } "M:Acme.Widget.op_UnaryPlus(Acme.Widget)"
下面列出可使用的一元运算符函数名称的完整集合:op_UnaryPlus、op_UnaryNegation、op_LogicalNot、op_OnesComplement、op_Increment、op_Decrement、op_True和 op_False。
- 二元运算符。
namespace Acme { class Widget: IProcess { public static Widget operator+(Widget x1, Widget x2) {...} } } "M:Acme.Widget.op_Addition(Acme.Widget,Acme.Widget)"
下面列出可使用的二元运算符函数名称的完整集合:op_Addition、op_Subtraction、op_Multiply、op_Division、op_Modulus、op_BitwiseAnd、op_BitwiseOr、op_ExclusiveOr、op_LeftShift、op_RightShift、op_Equality、op_Inequality、op_LessThan、op_LessThanOrEqual、op_GreaterThan和 op_GreaterThanOrEqual。
- 转换运算符具有一个尾随“~”,然后再跟返回类型。
namespace Acme { class Widget: IProcess { public static explicit operator int(Widget x) {...} public static implicit operator long(Widget x) {...} } } "M:Acme.Widget.op_Explicit(Acme.Widget)~System.Int32" "M:Acme.Widget.op_Implicit(Acme.Widget)~System.Int64"