注释
如果IDE提供注释格式,则尽量使用IDE提供的格式,否则使用"//"来注释。类、属性和方法的注释在Visual Studio中都使用输入"///"自动生成的格式。
1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Runtime.CompilerServices; 6 using System.Threading; 7 8 namespace System.Buffers 9 { 10 /// <summary> 11 /// Provides a resource pool that enables reusing instances of type <see cref="T:T[]"/>. 12 /// </summary> 13 /// <remarks> 14 /// <para> 15 /// Renting and returning buffers with an <see cref="ArrayPool{T}"/> can increase performance 16 /// in situations where arrays are created and destroyed frequently, resulting in significant 17 /// memory pressure on the garbage collector. 18 /// </para> 19 /// <para> 20 /// This class is thread-safe. All members may be used by multiple threads concurrently. 21 /// </para> 22 /// </remarks> 23 public abstract class ArrayPool<T> 24 { 25 /// <summary>The lazily-initialized shared pool instance.</summary> 26 private static ArrayPool<T> s_sharedInstance = null; 27 28 /// <summary> 29 /// Retrieves a shared <see cref="ArrayPool{T}"/> instance. 30 /// </summary> 31 /// <remarks> 32 /// The shared pool provides a default implementation of <see cref="ArrayPool{T}"/> 33 /// that's intended for general applicability. It maintains arrays of multiple sizes, and 34 /// may hand back a larger array than was actually requested, but will never hand back a smaller 35 /// array than was requested. Renting a buffer from it with <see cref="Rent"/> will result in an 36 /// existing buffer being taken from the pool if an appropriate buffer is available or in a new 37 /// buffer being allocated if one is not available. 38 /// </remarks> 39 public static ArrayPool<T> Shared 40 { 41 [MethodImpl(MethodImplOptions.AggressiveInlining)] 42 get { return Volatile.Read(ref s_sharedInstance) ?? EnsureSharedCreated(); } 43 } 44 45 /// <summary>Ensures that <see cref="s_sharedInstance"/> has been initialized to a pool and returns it.</summary> 46 [MethodImpl(MethodImplOptions.NoInlining)] 47 private static ArrayPool<T> EnsureSharedCreated() 48 { 49 Interlocked.CompareExchange(ref s_sharedInstance, Create(), null); 50 return s_sharedInstance; 51 } 52 53 /// <summary> 54 /// Creates a new <see cref="ArrayPool{T}"/> instance using default configuration options. 55 /// </summary> 56 /// <returns>A new <see cref="ArrayPool{T}"/> instance.</returns> 57 public static ArrayPool<T> Create() 58 { 59 return new DefaultArrayPool<T>(); 60 } 61 62 /// <summary> 63 /// Creates a new <see cref="ArrayPool{T}"/> instance using custom configuration options. 64 /// </summary> 65 /// <param name="maxArrayLength">The maximum length of array instances that may be stored in the pool.</param> 66 /// <param name="maxArraysPerBucket"> 67 /// The maximum number of array instances that may be stored in each bucket in the pool. The pool 68 /// groups arrays of similar lengths into buckets for faster access. 69 /// </param> 70 /// <returns>A new <see cref="ArrayPool{T}"/> instance with the specified configuration options.</returns> 71 /// <remarks> 72 /// The created pool will group arrays into buckets, with no more than <paramref name="maxArraysPerBucket"/> 73 /// in each bucket and with those arrays not exceeding <paramref name="maxArrayLength"/> in length. 74 /// </remarks> 75 public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket) 76 { 77 return new DefaultArrayPool<T>(maxArrayLength, maxArraysPerBucket); 78 } 79 80 /// <summary> 81 /// Retrieves a buffer that is at least the requested length. 82 /// </summary> 83 /// <param name="minimumLength">The minimum length of the array needed.</param> 84 /// <returns> 85 /// An <see cref="T:T[]"/> that is at least <paramref name="minimumLength"/> in length. 86 /// </returns> 87 /// <remarks> 88 /// This buffer is loaned to the caller and should be returned to the same pool via 89 /// <see cref="Return"/> so that it may be reused in subsequent usage of <see cref="Rent"/>. 90 /// It is not a fatal error to not return a rented buffer, but failure to do so may lead to 91 /// decreased application performance, as the pool may need to create a new buffer to replace 92 /// the one lost. 93 /// </remarks> 94 public abstract T[] Rent(int minimumLength); 95 96 /// <summary> 97 /// Returns to the pool an array that was previously obtained via <see cref="Rent"/> on the same 98 /// <see cref="ArrayPool{T}"/> instance. 99 /// </summary> 100 /// <param name="array"> 101 /// The buffer previously obtained from <see cref="Rent"/> to return to the pool. 102 /// </param> 103 /// <param name="clearArray"> 104 /// If <c>true</c> and if the pool will store the buffer to enable subsequent reuse, <see cref="Return"/> 105 /// will clear <paramref name="array"/> of its contents so that a subsequent consumer via <see cref="Rent"/> 106 /// will not see the previous consumer's content. If <c>false</c> or if the pool will release the buffer, 107 /// the array's contents are left unchanged. 108 /// </param> 109 /// <remarks> 110 /// Once a buffer has been returned to the pool, the caller gives up all ownership of the buffer 111 /// and must not use it. The reference returned from a given call to <see cref="Rent"/> must only be 112 /// returned via <see cref="Return"/> once. The default <see cref="ArrayPool{T}"/> 113 /// may hold onto the returned buffer in order to rent it again, or it may release the returned buffer 114 /// if it's determined that the pool already has enough buffers stored. 115 /// </remarks> 116 public abstract void Return(T[] array, bool clearArray = false); 117 } 118 }
-
类注释约定
/// <summary>
/// 类说明
/// </summary>
public class BinaryTree
-
类属性注释约定
/// <summary>
/// 属性说明
/// </summary>
public int NodesCount { get; private set; }
-
方法注释约定
/// <summary>
/// 方法说明
/// </summary>
/// <param name="parentNode">参数说明</param>
/// <returns>返回值说明</returns>
public int ComputeChildNodesCount(BinaryNode parentNode)
-
代码间注释约定
-
单行注释,注释行数<3行时使用
//单行注释
-
多行注释,2<注释行数<=10时使用
/*多行注释1
多行注释2
多行注释3*/
-
注释块,10<注释行数时使用,用50个*
/***************************************************
* 代码块注释1
* 代码块注释2
* ......
* 代码块注释10
* 代码块注释11
***************************************************/
-
何时写注释的约定
-
以下三种情况我们需要在所有的类、类属性和方法都必须按照上述格式编写注释
- 客户方对代码注释重视程度较高
- 我们需要提供代码注释自动生成的API文档。
- 目前编写的是公共核心模块
- 如果客户方没有对注释特殊要求,那么按照下文中讨论的只在需要的地方加注释。不要加无谓的注释。
-
常用注释标识的约定
这里约定下以后团队常用几种注释标识及含义:
//TODO: 我还没有处理的事情
//FIXME: 已知的问题
//HACK: 对一个问题不得不采用比较粗糙的解决方案
//XXX: 危险!这里有重要的问题
请团队成员自行在Visual Studio中配置FIXME和XXX为高优先级的Comments.
Steps: Tools->Options->Environment->Task List->Tokens->Add->OK
配置完成后,我们就能在Task List(Ctrl+w,t)窗口中的Comments选项中看到代码中存在的任务了。
-
关于何时使用“///”和“//”的约定
a. 对于需要让调用者知道的信息,使用“///”注释,以便让调用者能在调用时看到。
b. 对于代码内部实现细节,需要维护者知道的注释,使用“//”。减少调用者阅读时间。
-
不需要的注释
阅读注释会占用阅读真实代码的时间,并且每条注释都会占用屏幕上的空间。所以我们约定所加的注释必须是有意义的注释,否则不要浪费时间和空间。
区别要不要写注释的核心思想就是:不要为那些能快速从代码本身就推断的事实写注释。
-
不要为了注释而注释
有些人可能以前的公司对于注释要求很高,如"何时写注释"章节中的要求。所以很多人为了写注释而注释。
再没有特殊要求的情况下我们要禁止写下面这种没有意义的注释。
/// <summary>
/// The class definition for Account
/// </summary>
public class BinaryTree
{
/// <summary>
/// Total counts of the nodes
/// </summary>
public int NodesCount { get; private set; }
/// <summary>
/// All the nodes in the tree
/// </summary>
public List<BinaryNode> Nodes { get; set; }
/// <summary>
/// Insert a node to the tree
/// </summary>
/// <param name="node">the node you want insert into the tree</param>
public void InsertNode(BinaryNode node)
-
不要用注释来粉饰糟糕的代码
写注释常见的动机之一就是试图来使糟糕的代码能让别人看懂。对于这种"拐杖式注释",我们不需要,我们要做的是把代码改的能够更具有"自我说明性"。
记住:"好代码>坏代码+好注释"
如下面这段函数的注释
//Enforce limits on the reply as stated in the request
//such as the number of items returned, or total byte size,etc.
public void CleanReply(Request request,Reply reply)
既然知道这个函数名会让人很难读懂,那么为什么不直接改好名字呢?这样所有调用这个函数的地方都能很快速知道这个函数的作用,不用再跟进来看函数的作用。
public void EnforceLimitsFromRequestOnReply(Request request,Reply reply)
-
日志式注释
有人喜欢在每次编辑代码时,都在模块开始处加一条注释。这类注释就像是一种记录每次修改的日志。在很久以前这种记录对于维护还有意义。但是对于现在的源码控制来说,这些记录完全是冗余的,需要完全废除。
/***************************************************
* July-29-2014:Fix Bug-12345: Add new method to calculate nodes count
* July-20-2014:Fix Bug-11111: Add Insert new node method
* ......
* July-20-2014:Task-00001: Create BinaryTree class
***************************************************/
-
个人签名
//Added By XXXX
有人认为这种注释有助于不了解这段代码含意的人和他讨论。事实上确是这条注释放在那一年复一年,后来的代码和原作者写的源码越来越不一样,和XXXX也越来越没关系。
重申一下,TFS里都能看到这类信息,不要加在代码里。
-
位置标识
//AddNodePlace1
//AddNodePlace2
有人喜欢在代码注释里加入位置标识以方便他查找代码的位置。
现在的IDE都集成了这些功能,如VS中可以使用Bookmark(Ctrl+b,t)。
不要将这类注释加到代码中。
-
注释掉的代码
直接把代码注释掉是非常令人讨厌的做法。
其他人不敢删掉这些代码。他们会想代码依然在这一定是有原因的,而且这段代码很重要,不能删除。而且每个阅读代码的人都会去看一下这些被注释掉的代码中是否有他们需要注意的信息。
这些注释掉的代码会堆积在一起,散发着腐烂的恶臭。
-
需要的注释
-
记录你对代码有价值的见解
你应该在代码中加入你对代码这段代码有价值的见解注释。
如: //出乎意料的是,对于这些数据用二叉树比哈希表要快40%
//哈希运算的代价比左右比要大的多
这段注释会告诉读者一些重要的性能信息,防止他们做无谓的优化。
-
为代码中的不足写注释
代码始终在演进,并且在代码中肯定会有不足。
要把这些不足记录下来以便后来人完善。
如当代码需要改进时:
//TODO:尝试优化算法
如当代码没有完成时:
//TODO:处理JPG以外的图片格式
你应该随时把代码将来该如何改动的想法用注释的方式记录下来。这种注释给读者带来对代码质量和当前状态的宝贵见解,甚至会给他们指出如何改进代码的方向。
-
对意料之中的疑问添加注释
当别人读你的代码的时候,有些部分可能让他们有这样的疑问:"为什么要这样写?"你的工作就是要给这些部分加上注释。
如:
// 因为Connection的创建很耗费资源和时间,而且需要多线程访问,
// 所以使用多线程单例模式
public static Connection Instance
{
get
{
if(_instance==null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new Connection();
}
}
}
return _instance;
}
}
将注释生成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 报表中。 |
<param name='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>指示文本应该放在“另请参见”节中。 |
<see cref="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>* |
用以在文档中插入图片 |
<img src="src"/> |
src:图片的位置,相对于注释所在的XML文件 |
<file>* |
用以在文档中插入文件,在页面中表现为下载链接 |
<file src="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注释标签, 以及最后在帮助文档中的作用.
了解了标签的使用,在下篇文章中将告诉大家如何使用工具生成本文示例中的帮助文件.