NET中的规范标准注释(一) -- XML注释标签讲解
一.摘要
.Net允许开发人员在源代码中插入XML注释,这在多人协作开发的时候显得特别有用。 C#解析器可以把代码文件中的这些XML标记提取出来,并作进一步的处理为外部文档。 这篇文章将展示如何使用这些XML注释。 在项目开发中,很多人并不乐意写繁杂的文档。但是,开发组长希望代码注释尽可能详细;项目规划人员希望代码设计文档尽可能详尽;测试、检查人员希望功能说明书尽可能详细等等。如果这些文档都被要求写的话,保持它们同步比进行一个战役还痛苦。
为何不把这些信息保存在一个地方呢??最明显想到的地方就是代码的注释中;但是你很难通览程序,并且有些需要这些文档的人并不懂编码。最好的办法是通过使用XML注释来解决这些问题。代码注释、用户手册、开发人员手册、测试计划等很多文档可以很方便的从XML注释中获得。本文讲解.Net中经常使用的XML注释.主要使用C#语言j,.Net平台支持的其他语言使用的XML注释格式基本相同.并且在本系列文章的下一讲中讲解如何使用工具将XML注释内容转化为帮助文档.
二.XML注释概述
所有的XML注释都在三个向前的斜线之后(///)。两条斜线表示是一个注释,编译器将忽略后面的内容。三条斜线告诉编译器,后面是XML注释,需要适当地处理。
当开发人员输入三个向前的斜线后,Microsoft Visual Studio .NET IDE 自动检查它是否在类或者类成员的定义的前面。如果是的话,Visual Studio .NET IDE 将自动插入注释标记,开发人员只需要增加些额外的标记和值。下面就是在成员函数前增加三个斜线,自动增加的注释比如:
/// <summary> /// 得到指定酒店的酒店信息 /// </summary> /// <param name="hotelId">酒店Id</param> /// <param name="languageCode">语言码.中文为zh-cn</param> /// <returns>酒店信息对象</returns> [OperationContract] OutHotelInfo GetHotelInfoByHotelId(string loginName, string loginPassword, string hotelId, string languageCode);
这里嵌入的summary,param,returns标记仅仅是Visual Studio能够识别的一部分标记,然而在智能感知IntelliSense中,并没有把c#规范中所有的标记列出来,遗失的部分只能用手工插入。 这些手工标记是非常有用的,如果恰当地设置他们,对导出成外部说明文件将非常有帮助。
三.将注释生成XML文件
在代码中添加的注释信息, 可以单独提取出来, 生成XML文件. 在制作最后的帮助文件的时候会使用到这些注释XML文件.
默认情况下是不生成注释XML文件的.每个项目可以生成一个XML文件,需要我们在项目属性中进行设置:
如上图所示,在项目的"属性页"->"生成"中, 勾选"XML文档文件"复选框,即可在编译时生成注释XML文件.生成路径默认是和dll文件在同一个文件夹下,也可以自行修改.注意此处填写的是相对路径.
四.常见注释标签列表
注释的使用很简单,但是我们使用到的注释很少.这是因为大部分项目中注释的作用仅仅是给程序员自己看.如果想要生成类似MSDN这样的文档,我们需要了解更多的注释标签.下面是我整理的常用的注释标签:
标签名称 |
说明 |
语法 |
参数 |
<summary> |
<summary> 标记应当用于描述类型或类型成员。使用 <remarks> 添加针对某个类型说明的补充信息。 <summary> 标记的文本是唯一有关IntelliSense 中的类型的信息源,它也显示在 对象浏览器 中。 |
<summary> Description </summary> |
description:对象的摘要。 |
<remarks> |
使用 <remarks>标记添加有关类型的信息,以此补充用 <summary> 指定的信息。此信息显示在对象浏览器中。 |
<remarks> Description </remarks> |
description:成员的说明。 |
<param> |
<param> 标记应当用于方法声明的注释中,以描述方法的一个参数。 有关 <param> 标记的文本将显示在IntelliSense、对象浏览器和代码注释Web 报表中。 |
<paramname=‘name‘> description </param> |
name:方法参数名。将此名称用双引号括起来 (" ")。 description:参数说明。 |
<returns> |
<returns> 标记应当用于方法声明的注释,以描述返回值。 |
<returns> Description </returns> |
description:返回值的说明。 |
<value> |
<value> 标记使您得以描述属性所代表的值。请注意,当在 Visual Studio .NET开发环境中通过代码向导添加属性时,它将会为新属性添加 <summary> 标记。然后,应该手动添加 <value> 标记以描述该属性所表示的值。 |
<value> property-description </value> |
property-description:属性的说明 |
<example> |
使用 <example> 标记可以指定使用方法或其他库成员的示例。这通常涉及使用<code> 标记。 |
<example> Description </example> |
description: 代码示例的说明。 |
<c> |
<c> 标记为您提供了一种将说明中的文本标记为代码的方法。使用 <code> 将多行指示为代码。 |
<c> Text </c> |
text :希望将其指示为代码的文本。 |
<code> |
使用 <code> 标记将多行指示为代码。使用<c>指示应将说明中的文本标记为代码。 |
<code> Content </code> |
content:希望将其标记为代码的文本。 |
<exception> |
<exception> 标记使您可以指定哪些异常可被引发。此标记可用在方法、属性、事件和索引器的定义中。 |
<exception cref="member"> Description </exception> |
cref: 对可从当前编译环境中获取的异常的引用。编译器检查到给定异常存在后,将 member 转换为输出 XML 中的规范化元素名。必须将member 括在双引号 (" ") 中。 有关如何创建对泛型类型的 cref 引用的更多信息,请参见 <see> description:异常的说明。 |
<see> <seealso> |
<see> 标记使您得以从文本内指定链接。使用 <seealso> 指示文本应该放在“另请参见”节中。 |
<seecref="member"/> |
cref: 对可以通过当前编译环境进行调用的成员或字段的引用。编译器检查给定的代码元素是否存在,并将 member 传递给输出 XML 中的元素名称。应将 member 放在双引号 (" ") 中。 |
<para> |
<para> 标记用于诸如<summary>,<remarks> 或<returns> 等标记内,使您得以将结构添加到文本中。 |
<para>content</para> |
content:段落文本。 |
<code>* |
提供了一种插入代码的方法。 |
<code src="src"language="lan"encoding="c"/> |
src:代码文件的位置 language:代码的计算机语言 encoding:文件的编码 |
<img>* |
用以在文档中插入图片 |
<imgsrc="src"/> |
src:图片的位置,相对于注释所在的XML文件 |
<file>* |
用以在文档中插入文件,在页面中表现为下载链接 |
<filesrc="src"/> |
src:文件的位置,相对于注释所在的XML文件 |
<localize>* |
提供一种注释本地化的方法,名称与当前线程语言不同的子节点将被忽略 |
<localize> <zh-CHS>中文</zh-CHS> <en>English</en> ... </localize> |
五.注释与帮助文档
完善注释信息的最终目的就是为了生成MSDN一样的程序帮助文档,此文档将在项目整个生命周期中被各种角色使用:开发人员通过此文档维护程序, 测试人员通过此文档了解业务逻辑, 项目管理人员将此文档用作项目说明等等.
所以要了解列表中这些不常见的注释究竟有何作用,就要和最终的帮助文档关联起来.下面通过示例讲解注释标签在帮助文件中的作用.有关如何生成帮助文件,将在本系列下一篇文章中讲解.
先简单看一下帮助文件的样子.我们都看过MSDN帮助文档,使用注释XML文件生成的帮助文件后缀名是chm,打开后和MSDN基本一样:
本示例的命名空间是XmlCommentClassDemo, 其中包含两个类:
UserBL是包含方法的类.
UserInfo是一个模型类.里面只有UserId和UserName两个属性.
(1)类注释
看一下UserBL类的注释代码:
/// <summary> /// 用户对象业务逻辑层. /// </summary> /// <remarks> /// 2009.01.01: 创建. ziqiu.zhang <br/> /// 2009.01.23: 增加GetUserName和GetUserId方法. ziqiu.zhang <br/> /// </remarks> public class UserBL {...}
Summary标签的内容在命名空间类列表中显示,如上图.remarks标签的内容则显示在类页面中,如下图:
对比以前的注释规范,下面的注释是我们规定在创建一个新的文件时需要添加到头部的注释:
/*************************************************************************************** * * * * File Name : HotelCommentHeaderInfo.cs * * Creator : ziqiu.zhang * * Create Time : 2008-09-17 * * Functional Description : 酒店的点评头模型。包括酒店实体对应的点评头,酒店的OutHotelInfo信息 * ,酒店实体的Tag信息集合。 * * Remark : * * * * Copyright (c) eLong Corporation. All rights reserved. * ***************************************************************************************/
添加此注释块的目的很好.但是很难推广.因为这段注释并不能被编译器识别,也无法添加到注释XML文件中用于生成帮助文件. 格式不容易记忆,想添加的时候只能从别的复制过来后修改.公司缺少完善的Code Review机制所以最后很多文件都没有此注释块.
相比较使用.NET自己的注释语言,不仅"敏捷",而且会成为帮助文件中的描述.
(2)方法注释
类的注释比较简单.为了样式常用注释标签的效果, 我在方法的注释中使用了尽可能多的注释标签.代码如下:
/// <summary> /// 根据用户Id得到用户名. /// <para> /// 此处添加第二段Summary信息,在MSDN中很少使用.所以不推荐使用. /// </para> /// </summary> /// <remarks> /// 如果没有找到用户则返回null.<br/> /// <paramref name="userId"/> 参数为正整数.<br/> /// 用户Id模型属性定义参见<see cref="UserInfo.UserId"/><br/> /// 相关方法:<seealso cref="UserBL.GetUserId"/> /// </remarks> /// <param name="userId">用户Id</param> /// <returns>用户真实姓名</returns> /// <example> /// 返回用户id为100的用户真实姓名: /// <code> /// private string userName = string.Empty; /// userName = UserBL.GetUserName(100); /// </code> /// 返回的用户名可能为null,使用时要先判断:<br/> /// <c>if(userName!=null){...}</c> /// </example> /// <exception cref="System.ApplicationException"> /// 如果用户Id小于0则抛出此异常 /// </exception> public static string GetUserName(long userId) { string result = string.Empty; if (userId < 0) { throw new System.ApplicationException(); } else if (userId == 0) { result = null; } else { result = "Robert"; } return result; }
接下来通过图片进行详细讲解.首先是查看类成员时的截图:
点击方法后的截图:
需要注意的几点:
1) 最开始seealso标签添加在了remarks标签中,所以在See Also区域没有添加上方法的连接. 解决方法是把seealso标签放在summary标签中.
2) 异常类的cref属性需要设置成编译器可以识别的类, 这样才可以在帮助文档中点击.比如上面的System.ApplicationException异常点击后进入微软的在线MSDN查询.如果是自己定义的异常, 需要此异常类也在你的帮助文件中.一般提供注释XML和依赖DLL即可.
(3) 属性的注释
属性的注释也很简单.和类不同的地方在于属性要使用<value>标签而不是<remarks>进行描述:
private string m_UserName = string.Empty; /// <summary> /// 用户真实姓名 /// </summary> /// <value>用户真实姓名字符串.默认值为空.</value> public string UserName { get { return m_UserName; } set { m_UserName = value; } }
效果如图:
六.总结
本文讲解了.NET中的XML注释标签, 以及最后在帮助文档中的作用.
了解了标签的使用,在下篇文章中将告诉大家如何使用工具生成本文示例中的帮助文件.
出处:http://www.cnblogs.com/zhangziqiu/archive/2009/01/23/XmlComment.html
深入理解Java:注解(Annotation)自定义注解入门
要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法。
元注解:
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。Java5.0定义的元注解:
[email protected],
[email protected],
[email protected],
[email protected]
这些类型和它们所支持的类在java.lang.annotation包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。
@Target:
@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
使用实例:
@Target(ElementType.TYPE) public @interface Table { /** * 数据表名称注解,默认值为类名称 * @return */ public String tableName() default "className"; } @Target(ElementType.FIELD) public @interface NoDBColumn { }
注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。
@Retention:
@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy)有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)
Retention meta-annotation类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy的枚举类型值。具体实例如下:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Column { public String name() default "fieldName"; public String setFuncName() default "setField"; public String getFuncName() default "getField"; public boolean defaultDBValue() default false; }
Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理
@Documented:
@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Column { public String name() default "fieldName"; public String setFuncName() default "setField"; public String getFuncName() default "getField"; public boolean defaultDBValue() default false; }
@Inherited:
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。
实例代码:
/** * * @author peida * */ @Inherited public @interface Greeting { public enum FontColor{ BULE,RED,GREEN}; String name(); FontColor fontColor() default FontColor.GREEN; }
自定义注解:
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
定义注解格式:
public @interface 注解名 {定义体}
注解参数的可支持数据类型:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
Annotation类型里面的参数该怎么设定:
第一,只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型;
第二,参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;
第三,如果只有一个参数成员,最好把参数名称设为"value",后加小括号.例:下面的例子FruitName注解就只有一个参数成员。
简单的自定义注解和使用注解实例:
package annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 水果名称注解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitName { String value() default ""; }
package annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 水果颜色注解 * @author peida * */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitColor { /** * 颜色枚举 * @author peida * */ public enum Color{ BULE,RED,GREEN}; /** * 颜色属性 * @return */ Color fruitColor() default Color.GREEN; }
package annotation; import annotation.FruitColor.Color; public class Apple { @FruitName("Apple") private String appleName; @FruitColor(fruitColor=Color.RED) private String appleColor; public void setAppleColor(String appleColor) { this.appleColor = appleColor; } public String getAppleColor() { return appleColor; } public void setAppleName(String appleName) { this.appleName = appleName; } public String getAppleName() { return appleName; } public void displayName(){ System.out.println("水果的名字是:苹果"); } }
注解元素的默认值:
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,一次表示某个元素不存在,在定义注解时,这已经成为一个习惯用法。例如:
1 package annotation; 2 3 import java.lang.annotation.Documented; 4 import java.lang.annotation.ElementType; 5 import java.lang.annotation.Retention; 6 import java.lang.annotation.RetentionPolicy; 7 import java.lang.annotation.Target; 8 9 /** 10 * 水果供应者注解 11 * @author peida 12 * 13 */ 14 @Target(ElementType.FIELD) 15 @Retention(RetentionPolicy.RUNTIME) 16 @Documented 17 public @interface FruitProvider { 18 /** 19 * 供应商编号 20 * @return 21 */ 22 public int id() default -1; 23 24 /** 25 * 供应商名称 26 * @return 27 */ 28 public String name() default ""; 29 30 /** 31 * 供应商地址 32 * @return 33 */ 34 public String address() default ""; 35 }
定义了注解,并在需要的时候给相关类,类属性加上注解信息,如果没有响应的注解信息处理流程,注解可以说是没有实用价值。如何让注解真真的发挥作用,主要就在于注解处理方法,下一步我们将学习注解信息的获取和处理!