When you write XML doc comments in your .NET code for your internal purposes, you usually don't care about their formal perfectness. All you need is to be clear and precise.
The situation is however different, when it comes to commenting an API that will be publicly available to the end-users. The documentation must be clear, formally correct and consistent.
I'm not going to talk about a grammar, English is not my strong point. But I will show you the most common mistakes and some simple rules that can make your documentation even better. The list is by no way complete. I collected it from my experiences with my own or my clients' APIs and from what I have observed in MSDN. The points are in random order. All improvements can be done manually without any tool, but where appropriate, I will show how they can be simplified with our VSdocman, especially with its WYSIWYG comment editor.
1. Don't hardcode true, false, null, Nothing, static and other language specific keywords.
2. Don't put “obsolete” info directly in XML comments, use ObsoleteAttribute.
3. Use a definition list for describing predefined values
4. Document each enumeration item, don't place its description to the parent enumeration comments
5. Don't duplicate enum description in methods that use the enumeration
6. Use <see> if you're referring to another member
7. Use <paramref> if you're referring to another parameter in the same method
8. Member summary is a description, not a command
1. Don't hardcode true, false, null, Nothing, static and other language specific keywords.
Very often you need to mention true, null and other special keywords in the text, especially in description of parameters and return values. Remember that these keywords are language specific. The true in C# is True in VB .NET. Even more confusing could be Nothing in VB. It's null in C#. Clients of your API may use any .NET language, so don't force them to use your specific nomenclature. XML comments can use langwords for this situation.
Instead of the following:
<returns> Retrieved result or null if the result was not found. </returns>
Use:
<returns> Retrieved result or <see langword="null"/> if the result was not found. </returns>
The following table lists allowed values of langword parameter and a corresponding (English) text:
Langword |
Text |
null |
null reference (Nothing in Visual Basic) |
true |
true |
false |
false |
static |
static (Shared in Visual Basic) |
abstract |
abstract (MustInherit in Visual Basic) |
sealed |
sealed (NotInheritable in Visual Basic) |
virtual |
virtual (Overridable in Visual Basic) |
VSdocman editor offers the menu for automatic insertion:
2. Don't put “obsolete” info directly in XML comments, use ObsoleteAttribute.
When a member is deprecated, this fact should be mentioned in documentation. However, you don't need to put this info in your XML comments. You should apply the ObsoleteAttribute to the member and provide a description there. This info will automatically appear in generated documentation as well.
So if you have the following code:
/// <summary> /// This is a sample method /// </summary> [Obsolete("This method is obsolete. Use the new Method2 instead which is thread safe.")] public void Method1() { }
The generated documentation (with VSdocman) will look as follows:
3. Use a definition list for describing predefined values
Usually, you will use enumerations if you want to specify a limited set of values. But there are cases when you cannot, whether for backward compatibility or the value is of type String or whatever else. A definition list is a standard XML doc construct and it is ideal for such situation.
For example, let's have the following comment for ErrorNumber parameter:
<param name="ErrorNumber"> 0 = Operation OK, 1 = General error, see ErrorMessage, 2 = No item at row number, 4 = Was not called from an estimate</param>
In Vsdocman comment editor it looks like:
You can see that while it looks quite good in the source code (newlines), it is not that cool in the WYSIWYG editor and thus in generated documentation. Let's convert it to a definition list. This task can be done manually, but it is the easiest in the comment editor.
If you do it manually, the XML comment will be:
<param name="ErrorNumber"><list type="bullet"> <item> <term>0</term> <description>Operation OK</description> </item> <item> <term>1</term> <description>General error, see ErrorMessage</description> </item> <item> <term>2</term> <description>No item at row number</description> </item> <item> <term>4</term> <description>Was not called from an estimate</description> </item> </list></param>
It's easier with VSdocman. First, make the paragraph a bulleted list, because a definition list is just a special case of bulleted list. Place cursor on the text and click “Bullets” button on the toolbar.
Press Enter after each value so that it become a separate list item.
Remove = character in the first item. It's responsibility of the generated output to correctly format a definition list. Select 0 character and make it a definition term by pressing “Definition Term” button on the toolbar.
The definition term and its description will be automatically formatted.
Do the same with all remaining list items. You'll get the final definition list:
And here's how it will look in HTML format generated with Vsdocman:
4. Document each enumeration item, don't place its description to the parent enumeration comments
I often see enums documented like this (this one is in VB but it's the same in C#):
''' <summary> ''' Specifies how the <see cref="UserInformation"/> is filtered. ''' </summary> ''' <remarks> ''' AllLevels: Show all available levels<br/> ''' CurrentLevelOnly: Show only the current level and its direct children<br/> ''' CurrentAndParentLevels: Show only the current level and its parents<br/> ''' CurrentAndAllChildLevels: Show the current level and all its children (children, ''' grandchildren, etc...) ''' </remarks> Public Enum WhichUserInformation AllLevels CurrentLevelOnly CurrentAndParentLevels CurrentAndAllChildLevels End Enum
You can see that all items are documented directly in the enum comment. This is not good for maintainability (for example, you remove or add an item and forget to update the comment), nor for IntelliSense, nor for generated documentation.
The correct way is to document each item:
''' <summary> ''' Specifies how the <see cref="UserInformation"/> is filtered. ''' </summary> Public Enum WhichUserInformation ''' <summary> ''' All available levels. ''' </summary> AllLevels ''' <summary> ''' Only the current level and its direct children. ''' </summary> CurrentLevelOnly ''' <summary> ''' Only the current level and its parents. ''' </summary> CurrentAndParentLevels ''' <summary> ''' The current level and all its children (children, ''' grandchildren, etc...). ''' </summary> CurrentAndAllChildLevels End Enum
Now the IntelliSense is OK and the table with enum values in generated documentation contains correct descriptions.
5. Don't duplicate enum description in methods that use the enumeration
This one is closely related to the previous one. When you have properly documented enum, you don't need to do it on each place where it's used.
For example, let's have the WhichUserInformation enum from the previous example. Now we have a method that uses it as a parameter type (only the parameter comment is shown here):
''' <param name="WhichInformation"><para>Selects which information:</para> ''' <para>AllLevels: Show all available levels<br/> ''' CurrentLevelOnly: Show only the current level and its direct children<br/> ''' CurrentAndParentLevels: Show only the current level and its parents<br/> ''' CurrentAndAllChildLevels: Show the current level and all its children (children, ''' grandchildren, etc...)</para></param> Public Function GetUserInfo(WhichInformation As WhichUserInformation) As List(Of UserInformation)
The description of possible values of the WhichInformation is redundant. We can completely remove it:
''' <param name="WhichInformation"> ''' Specifies which information will be retrieved. ''' </param> Public Function GetUserInfo(WhichInformation As WhichUserInformation) As List(Of UserInformation)
The comment is cleaner and we didn't lost any information. IntelliSense will offer the enum values and their descriptions. Generated documentation automatically contains the link to the enum documentation where you can read it.
6. Use <see> if you're referring to another member
This one is the most common case I've been seeing. It's impossible not to mention another member, e.g. another class or a method in the same class, in our documentation. Most of the time, people just put its name as a plain text, maybe they highlight it somehow in better case. In the following example, we can see two references to the Close() method, both displayed as a bold text:
/// <summary> /// Opens the connection with specified parameters. /// </summary> /// <remarks> /// The connection remains opened until explicitly closed with /// the <b>Close()</b> method. It's up to the caller to call /// the <b>Close()</b> method when the connection is no longer needed. /// </remarks>
While this looks good, we are forcing the reader to find and navigate to the method's help manually because there's no way to click on any link directly.
Documentation generators help us in many cases and generate some links automatically. For example, for parameter types, types in the Syntax section, inheritance tree, see also, etc. But we need to create links explicitly in normal text. We'll use the <see> comment tag. It's not that difficult even without the comment editor. Visual Studio and Vsdocman recognize also partial and incomplete cref identifiers. Usually, only the member name is enough, no need to enter the full name. Moreover, IntelliSense or VSdocman comment editor will help us with it.
A link to the referenced member is good thing but on the other hand we don't want to bloat our documentation with repeating links. So I recommend to use a link only at the first place the referenced member is mentioned and then use just plain text if the member is mentioned too often.
The fixed comment could look as follows:
/// <summary> /// Opens the connection with specified parameters. /// </summary> /// <remarks> /// The connection remains opened until explicitly closed with /// the <see cref=”Close”/> method. It's up to the caller to call /// the Close method when the connection is no longer needed. /// </remarks>
7. Use <paramref> if you're referring to another parameter in the same method
It's very common that you refer to another parameter. Many people don't do anything with it, some do highlight it as bold or italics. But the correct way is to use paramref XML doc tag, which is designed for exactly this purpose.
Let's have the ErrorMessage parameter. It is mentioned in some other text:
You can whether convert it to paramref manually, or use the comment editor. Select the text and press “Insert Paramref” button on the toolbar.
You'll get automatically formatted parameter reference:
Its source code is:
… General error, see <paramref name="ErrorMessage"/> …
You can use the same mechanism for referencing generic type parameters. Just use <typeparamref> comment tag instead of the <paramref>.
8. Member summary is a description, not a command
You describe what a method does, you don't give it an instruction.
So instead of
<summary> Return unique identifier of the item being at the given position. </summary>
use
<summary> Returns unique identifier of the item being at the given position. </summary>
9. Don't forget a period at the end of a block.
Each top-level comment elements should contain a sentence or several sentences. As such, they need a period at the end.
So instead of
<summary> Returns unique identifier of the item being at the given position </summary>
use
<summary> Returns unique identifier of the item being at the given position. </summary>
10. Be consistent with your wording.
Use the same phrases and terms throughout your entire documentation.
For example, when you refer to an empty string use “an empty string” phrase instead of “” or blank or anything else.