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定義的元注解:
1.@Target,
2.@Retention,
3.@Documented,
4.@Inherited
這些類型和它們所支持的類在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作為默認值是一種常用的做法。這個約束使得處理器很難表現一個元素的存在或缺失的狀態,因為每個注解的聲明中,所有元素都存在,並且都具有相應的值,為了繞開這個約束,我們只能定義一些特殊的值,例如空字符串或者負數,一次表示某個元素不存在,在定義注解時,這已經成為一個習慣用法。例如:
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 FruitProvider { /** * 供應商編號 * @return */ public int id() default -1; /** * 供應商名稱 * @return */ public String name() default ""; /** * 供應商地址 * @return */ public String address() default ""; }
定義了注解,並在需要的時候給相關類,類屬性加上注解信息,如果沒有響應的注解信息處理流程,注解可以說是沒有實用價值。如何讓注解真真的發揮作用,主要就在於注解處理方法,下一步我們將學習注解信息的獲取和處理!