|n every project, there is someone who is not happy with the documentation. The team leads want more comments in the source, technical writers want more written information about the code design, Quality Assurance wants to see functional specifications, and so on. If all of those documents are actually written, you still have the battle of keeping all of them synchronized.|
Wouldn't it be nice if all this information were kept in one location? The obvious place to put this information is in the source code itself; this makes it easy to modify the docs along with the code. But it is hard enough to go through someone else's program when you know how to code, let alone when you are less technically savvy. Also, adding code documentation takes time.
This article will show you how to solve many of those problems using XML comments. Code comments, user manuals, developer manuals, test plans, and many other documents can be generated from a single source using XML tags. I'll explain how to insert XML comments and enable exportation of these comments to another file. I'll then discuss each of the available tags and the format of the XML file where the tags are used and walk through an example of using XML comments and XSLT to generate help files.
XML Comments All XML comments begin with three forward slashes (///). The first two slashes signify a comment and tell the compiler to ignore the text that follows. The third slash tells the parser that this is an XML comment and should be handled appropriately.
When the developer types the three forward slashes, the Microsoft® Visual Studio® .NET IDE checks to see if it precedes an identifiable type or type member definition. If it is identifiable, then the Visual Studio .NET IDE will automatically insert a few comment tags. The developer then adds additional tags and values as needed. For example, here are the XML tags that are generated when the three forward slashes are entered before this member function that has a parameter:
The tags inserted here represent only a couple of many that the Visual Studio .NET IDE already knows. While the current implementation of IntelliSense® for XML comments does not show all the tags listed in the C# specification, the missing tags can always be inserted manually.
/// <param name="strFilePath"></param>
public void LoadXMLFromFile(string strFilePath)
Once comments are inserted, it would be useful to do something with them. If you set things up correctly (as I'll explain shortly), this usually just means exporting all the text that comes after the three forward slashes into an external file.
There are also several predefined XML comment tags that the parser knows. When these tags are encountered (and have been used correctly by the developer), the parser will format the tags' output to a text file. If a tag that is not predefined is encountered, it is just written verbatim along with any text to the external file. This means that you can make up your own tags or you can even use known tags recognizable from another source (such as HTML) in the comments.
When exportation is set up correctly, the C# parser extracts the XML comments, does formatting if needed, and places them into an XML file of your choice. You can set the location and file name of the XML document file, and enable exportation, through your project's property pages. To do this, follow these four steps:
In my example I used GiveHelpDoc.xml as the document name (see Figure 1). If no value is placed here for the path and file name, which is the default setting, then XML comments will not be extracted to a separate file.
- Open the property page for the project, usually by right-clicking on the project in the Solution Explorer, and click Properties.
- After the dialog has opened, click the Configuration Properties folder.
- Click the Build option.
- In the right pane, there will be a property field called XML Documentation File. Set this to the path and file name of the desired file. The path entered is relative to the project directory, not absolute.
Recognized Tags I have classified the XML comment tags into two categories. The first set of tags, which I'll call primary tags, always starts a group of XML comment tags. They are never embedded within other tags. The second set contains tags that are used within primary tags as modifiers to the text. I call them support tags. To distinguish between support tags and HTML tags, the support tags are lowercased and HTML tags are uppercased. Figure 2 shows examples of the primary and support tags that I will be discussing in this article.
There are nine primary tags: <remarks>, <summary>, <example>, <exception>, <param>, <permission>, <returns>, <seealso>, and <include>.
In this context, the <remarks> tag is used to describe a type such as a class:
The C# documentation recommends using <remarks> to describe a type and a <summary> tag to describe a type member. Oddly, if you start a comment before a type using the /// combination, the Visual Studio .NET IDE will still insert a <summary> tag. Therefore, <remarks> tags need to be inserted manually.
/// Class that contains functions to do
/// transformations to help files.
The <summary> tag is the tag found most often in a C# source file. This tag is used to describe type members, including methods, properties, and fields:
The <summary> tag can describe types as well, but it is not recommended. The XML comment documentation recommends that the <remarks> tag be used for describing types.
/// This XmlDocument based attribute contains the
/// XML documentation for the project.
The <example> tag is used to mark the beginning of an example showing how to use the item. The example can be any valid text, but most often it is a snippet of code:
If code is used, it is usually marked with a <code> tag. The <code> tag will be discussed in the section on support tags.
/// // create the class that does translations
/// GiveHelpTransforms ght = new GiveHelpTransforms();
/// // have it load our XML into the SourceXML property
The <exception> tag documents the exceptions, if any, that the item may throw. If more than one exception may be thrown, then more than one of these tags may be used. Unlike most of the tags, the <exception> tag has one attribute, cref. The value of this attribute is the name of the exception that could be thrown. This must be a valid class because the C# parser will verify it and extract the context information to put into the XML documentation. I will explain this later.
I did not throw any exceptions in the code for this article, but here is an example of how the cref attribute can be used with the <exception> tag:
The <param> tag describes the parameters of a method or property. It is automatically inserted by the IDE when using the three forward slashes before a method. The <param> tag has one attribute, name, which is simply the same name that the parameter has in the source:
/// <exception cref="SampleException">
/// Normally, a discussion on any exceptions thrown would go here.
Member access is identified with the <permission> tag. The text that is assigned to it sets permission-related descriptions. There is no requirement for the value for this tag. Permission is one possibility, with values such as public, private, protected, and so on. Scope is another possible value, with information about whether the method is static. However, you are free to put whatever value you must have here.
/// <param name="strFilePath">The path to the file containing the
/// source XML.</param>
The <permission> tag has one attribute—cref. The documentation describes the use of cref in this context as "a reference to a member or field that is available to be called from the current compilation environment." It is usually set to System.Security.PermissionSet:
The <returns> tag is similar to the <param> tag, but it's used to describe what the method or property returns:
/// <permission cref="System.Security.PermissionSet">Public
The <seealso> tag specifies "other" links related to the same topic. This tag usually does not include a text value, just a cref attribute that specifies a reference to a symbol. This could be a type, member, field, and so on.
/// <returns>The HTML for a list of types based on the XML
The XML compiler will identify the content of the symbol and use it accordingly in the compiled XML documentation. I'll discuss this again in the section on the XML documentation file.
/// <seealso cref="GiveMemberListHTMLHelp"/>
When XML comments are extracted from the code by the compiler, any file specified in the <include> tag will be expanded and the comments inside it will be used as if that had been included inline. Because the compiler finds them, you can keep comments in an external file. This can help organize your code, but then the comments are not readily available. I would never use the <include> tag for this reason, but if your comments are lengthy, you may find the trade-off acceptable.
Several attributes must accompany the <include> tag to specify the external file. The file attribute is the name of the file using relative or fully qualified paths. The include file itself is an XML document that holds XML comments. The path attribute is an XPath statement that points to the parent element of the XML comments in the external document, as you can see here:
Figure 3 shows the code for MyXMLCommentFile.xml. Note that in this example I used XPath to specify the parent element that contained the XML comments. The actual format of the file is up to the developer. I chose to use a format similar to the XML document file generated by the compiler, which I will discuss later.
/// <include file='MyXMLCommentFile.xml'
public class MyExampleClass
/// <include file='MyXMLCommentFile.xml'
public string MyExampleMethod(string strReturnThis)
Support Tags There are eleven support tags: <c>, <code>, <list>, <listheader>, <item>, <term>, <description>, <para>, <paramref>, <see>, and <value>.
The <c> tag marks a line of text as code. It is usually used inline in descriptive text.
The <code> tag also defines a section of text as code. It is often used within an <example> tag block (as shown earlier). The <code> tag is similar to the <c> tag, but <c> is used for a single line of code while <code> is used for a block of code. It specifies that the formatting of the text in the comments needs to remain the same:
/// The source XML is loaded into the <see cref="SourceXML"/>
/// property (e.g. <c><I>obj</I>.SourceXML =
/// "<I>XML goes here</I>"</c>).
The <list> tag is used in other comment tags to define a specialized list. The type of list is defined by the type attribute, which can have the values bullet, number, or table. Within the <list> tag, there are tags that denote the components of the list, as you can see in this example:
/// // create the class that does translations
/// GiveHelpTransforms ght = new GiveHelpTransforms();
/// // have it load our XML into the SourceXML property
The <listheader> tag holds header information for the list (see the previous example). It is typically used for a table of list types.
/// <list type="table">
/// <term>Help Page</term>
/// <description>Function to call</description>
/// <item><term>List of Types</term>
/// <item><term>List of members</term>
/// <item><term>Help for a single member</term>
As you probably guessed, the <item> tag identifies each item in the list. It can stand alone or wrap <term> and <description> tags. The <term> and <description> tags are always children tags of the <listheader> tag or <item> tags. They are always used in pairs and their function is obvious.
The <para> tag is used to identify a new paragraph. It is very similar to the <P> tag in HTML. You should definitely use this tag to break up long comment sections:
If text within a comment needs to have a parameter specially identified, then <paramref> is the tag to use. It is inserted in the text in the location where the parameter text should appear. The main purpose of this tag is to identify the name of a parameter that should be formatted in a special way. It has one attribute, name, which is the name of the parameter that would appear in the text, usually formatted in a special way:
/// <summary>This is a summary.
/// <para>This is a new paragraph.</para>
The <see> tag is used within the text of other comment tags to specify a hyperlink. It is used inline as part of the text and usually just includes one attribute, cref:
/// Loads the XML documentation in the file specified by
The cref attribute specifies a reference to an existing symbol. See the description for the <exception> tag for more information.
/// One of the associated member functions (<see
/// <see cref="GiveMemberListHTMLHelp"/>, <see
/// is called to initiate and then return the transformation.
The <value> tag defines the meaning of a property member. It is used just like the <remarks> tag is used for classes and the <summary> tag is used for other members.
/// The SourceXML property contains the XML that will be used in
/// the transformations by the member functions for this class.
The XML Document File Earlier, I explained how to set up your project to begin exporting XML comments. Figure 4 shows a portion of the XML document file I generated after compiling my sample project. If you take a look at this and the original XML comments in Figure 2, you will see that some of the exported comments have been formatted.
Processing the tags shown in Figure 5 requires some specific formatting. Almost all the tags listed there are formatted by having their cref attribute expanded. This means the C# parser takes the original value of the cref attribute and does two things to it. First, it identifies the classification of the value, such as whether it is a type or property. Second, it expands the value to a fully qualified name. For example, an expanded cref with a value of "MyMember" (that is, cref="MyMember") might look something like this after parser formatting:
The M: represents the classification. The compiler classifies members one of five ways when placing their reference in the XML documentation. The classification is created by prefixing a classification value to the reference's name in the documentation. Figure 6 shows the prefix values used.
The other parts of the expanded name in the previous example are also significant. The value "MyNamespace.MyType.MyMember" is the fully qualified name as found by the C# parser. The name can be divided into parts using a period as a delimiter. Each substring has a fixed meaning. "MyNamespace" represents the namespace, "MyType" represents the type, and "MyMember" is the name of the member.
If the cref value cannot be resolved, the compiler will prefix the original cref value with "!:" (see Figure 7). This encoding is not only used in expansions of the cref attributes. Members' names are also referenced in the XML documentation using this format, as you will see next.
The basic high-level structure of the XML document file is shown in the following document:
The first line is the standard header followed by the root element <doc>. Next is the <assembly> tag. Under the <assembly> tag are the <name> tags, which identify the names of the assemblies being built from the code of the project. Thus, there is usually just one <name> entry. The value used by the C# compiler comes from the project name; it is not controlled by any XML in the source.
XML Comments are here
Next comes the <members> block, which contains entries for each member that had XML comments associated with it. Each member is identified by its own <member> tag.
The <member> tag has one attribute: name. The C# parser generates the value for this attribute based on the name of the member that the comment tags are associated with. I have already described the naming convention used. The remaining XML that is wrapped by the <member> tag consists of the actual XML comments that were in the code—some of which have been modified, as I have already mentioned.
Generating Help Files It would be impossible to list everything you could do with a document full of XML comments, but a common use is to apply XSLT to the XML to generate HTML help pages.
I created a project called GiveHelp that consists of a simple class named GiveHelpTransforms, several ASP pages, and XSLT to use for transformations (see the code download for this article at the link at the top of this article). The Web application takes XML documentation available in a local XML file and creates navigable help files. The Web application was designed to take XML documentation from any application and create help pages.
The ASP pages in the project are used as a means to easily display the results of the transforms in a browser. They also serve to interface with the GiveHelpTransforms class to initiate the XML-to-HTML transformations.
There are two reasons for the existence of the GiveHelpTransforms class. First, other programs can have an existing infrastructure built to do XML-to-HTML conversions using the XSLT written for this project. Second, I needed a class in which I could generate the XML documentation that I could use to demonstrate this project. So it might sound confusing at first, but for my examples the application is using its own XML documentation to generate help files about how a developer would use it.
Transformations In this section, I will assume you already understand XSLT. Therefore I will only touch upon the XSLT I used to convert the XML document into HTML help pages.
I divided my help system into three levels. The top level is the Type List. This HTML page lists all of the types in the XML documentation file along with their descriptions. A sample view of this page is shown in Figure 8. Each type listed links to a page that lists all the members of the type. This middle level is Member List. Given a type, this page will list all the members and their description. Clicking on one of the members will navigate to the next level, Type Member, which gives detailed help on a specific member including a description, permissions, parameters, exceptions, an example, and links to more info.
The XSLT used in this section can be found in the GiveTypeHelp.xsl file in the code download. It converts the XML comments into HTML to display in a browser. To display the list of types in HTML, a definition list is created using the <DL> tag pair with the type name placed in a <DT> tag pair along with its description in a <DD> tag pair. This shows the type name at the left of the page with its description indented under it.
The classes have to be extracted from the document. Remember, all types have names that are prefixed by "T:". To select these, the XSLT statement is written as follows:
This line can be found at the top of the code in the block that begins the processing of the <doc> element. Later in the code there is a block that is wrapped like this:
This is the block that is called by the <xsl:apply-templates> tag used in the previous code. These two statements tell the XSLT processor to process only those members that are types.
To format each type and place it on the HTML page, the name of each type has to be extracted from the name, and the description of the type must be contained. Figure 9 shows how the <DT> and <DD> tag blocks are set up to do this.
In between the <DT> tags, a link is created that points to the ASP page that generates HTML for the Member List level. In between the <DD> tags, the <remarks> or <summary> tags are processed. I will explain what happens during this processing in the next section as all of the XSLT sheets are similar in this regard. Figure 8 shows what the result would look like.
Member List When a type is selected in the Type List level in my example, the Web browser is redirected to the GiveTypeMemberListHelp.aspx page. Here is where you find the list of members in the type. An excerpt of the XSLT source is shown in Figure 10 (for the full source, see the code download).
When the GiveTypeMemberListHelp.aspx page is requested, it accepts a parameter that names the fully qualified type (for example, /GiveTypeMemberListHelp.aspx?Type=MyNamespace.MyType). The page retrieves this name and, when it executes the XML-to-HTML transform, it passes the parameter value into the XSLT processor as the value to the WhichType parameter. The XSLT for the parameter looks like the following:
This parameter is used by the XSLT to select only <member> tags that describe members that belong to my type. Here is how this is done in XSLT:
<xsl:param name="WhichType" />
Specifically, this code is calling the templates for Fields, Properties, Methods, and then Events. I use XPath to apply templates to the <member> elements that have name attributes that fit the pattern. The pattern is the format you saw before:
with(@name,concat('F:',$WhichType))]" mode="fields" />
with(@name,concat('P:',$WhichType))]" mode="properties" />
with(@name,concat('M:',$WhichType))]" mode="methods" />
with(@name,concat('E:',$WhichType))]" mode="events" />
The templates for each member look very similar, so I only will explain one, the template for methods from GiveTypeMemberListHelp.xsl (see Figure 10). The first tag, <xsl:if>, tests to see if this is the first element. If it is, then a header is written. The section between the <DT> tags creates the link to the page that gives more detailed information about that member. The section between the <DD> tags retrieves the <summary> or <remarks> block and processes it. In the next section I will explain how the <summary> and <remarks> tags are processed.
Type Member The GiveTypeMemberHelp.xsl file contains the XSLT discussed in this section. Like the Member List level, the Type Member level has a parameter. In addition to the type, the fully qualified member name is also passed in:
The type can be extracted from the member name, as you can see in the following XSLT:
<xsl:param name="WhichMember" />
A little complicated, but it does the job.
<xsl:param name="WhichType" select="concat
(concat( substring-before($WhichMember,'.'), '.'),
The XSLT processing begins with the following lines:
Processing the <name> element is pretty straightforward. The second <xsl:apply-templates> element begins processing of the desired member that was passed into the stylesheet via the $WhichMember variable. This line (from Figure 11) is where processing of the member's XML comments begins:
<xsl:apply-templates select="./assembly/name" />
Most of this should be familiar to those of you who are already acquainted with XSLT.
Besides inserting various headers, the primary tags that belong to this member will be processed. This is done with elements such as <xsl:apply-templates select="summary" /> and <xsl:apply-templates select="param" />, which can be seen if you look at the source file GiveTypeMemberHelp.xsl. This is done at all the help levels, and I will discuss how it works shortly.
Processing the Tags After the initial formatting, such as headings and alignments, the stylesheet directs the XSLT processor to begin formatting the primary and support XML comment tags. This process and the results are the same throughout all the different stylesheets found in this project.
In the different stylesheets, a determination will be made as to which primary tags to process. In the Type List and Member List levels, it's just the <summary> and <remarks> tags. In the Member level, it is all the primary tags. You can see where this processing begins in the XSLT with select statements such as <xsl:apply-templates select="primary tag" />, where "primary tag" is the name of the primary tag to process.
Once this statement is executed, the processor jumps to a block that looks something like this:
This block is where the HTML tags surrounding the primary tag's child nodes will be created. What all of them have in common is the call to <xsl:apply-templates />. It is this call that begins the processing of the child nodes, otherwise called support tags.
<xsl:template match="primary tag">
The support tags usually translate into an HTML equivalent. For example, the <c> tag's template looks like the following:
This basically translates the <c> tag to the HTML <CODE> tag. The XML comment tags translate into the HTML equivalents that are shown in Figure 12.
Figure 13 shows a sample of the XML in Figure 4 translated into HTML using the XSLT in Figure 11.
Conclusion Although tools and utilities to extract comments from source code have existed for quite some time, they have never become widely used. Much of this can be attributed to the difficulty in using these tools and the lack of integration with core development products. XML-based comments in C# overcome these obstacles through language and editor integration along with the use of XML technology. Even more power can be realized by taking the extracted XML comment data and transforming it to other desired data formats such as HTML.