C# .NET provides a standard mechanism for programmers to inline XML documentation in their programs. Classes, functions, properties and more can be augmented with comments, and the build system creates amalgamated XML files accompanying the program output. A variety of tools such as Sandcastle can build help files from the data. Visual Studio 2005 and 2008 also supports the scheme with keyboard shortcuts and optional build warnings for missing comments.
-
/// <summary>
-
/// An example C# class
-
/// </summary>
-
/// <remarks>
-
/// This class illustrates how a class can be marked up with inline C# comments
-
/// </remarks>
-
class SomeExampleClass
-
{
-
/// <summary>
-
/// An example of a property
-
/// </summary>
-
public int ExampleProperty
-
{
-
get { return somePrivateVar; }
-
}
-
}
Unfortunately, many programmers have observed that there’s no way to discover these comments at runtime by reflection. All manner of alternative information can be accessed with reflection – but not the XML comments.
The solution
This documentation can be discovered at run time with a little extra code, as I will demonstrate. The reason the comments can’t be discovered by reflection alone is because they are not included in the .NET assemblies (.EXE or .DLL files), but are conventionally included as .XML files to accompany the assembly files.
I’ve provided a simply class library for .NET 2.0 called DocsByReflection that will when possible return an XmlElement that describes a given type or member. This can be used to easily extract comments at runtime. Here’s an example of how:
-
XmlElement documentation = DocsByReflection.XMLFromMember(typeof(SomeExampleClass).GetProperty("ExampleProperty"));
-
Console.WriteLine(documentation["summary"].InnerText.Trim());
This would ouput “An example of a property”, extracted from the code comments in the code fragment at the top of the article.
The class works by locating the .XML file accompanying the assembly that defines the type or member (discovered using reflection). Then the .XML file is loaded into an XmlDocument, and the ‘member’ tags are scanned to find the one that refers to the target type or member. This tag is returned as an XmlElement which contains the free form XML that these comments can contain (e.g. including HTML markup).
This method relies on the existence, on disk, of the generated XML. It must have the same name and location as the generated .EXE or .DLL accompanying it – with only the extension changed. This will happen by default when building with Visual Studio, once documentation is enabled (see below). If you are distributing your program and expect the distributed version to discover the comments in run time, you must also distribute the XMLs alongside the executables. This is commonly seen in any case. Note that many of the framework executables (for instance those found in C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727) are accompanied by documentation XML files.
How to use
Download the library source as a zip file here, or use Subversion to update to the very latest version here and here.
(Updated 18/3/08 for bug fix - see comments).
Load the DocsByReflectionDemo.sln to see a simple example of how to use the library. This gathers and prints information about a class type, some functions, a property and a field.
Using in your own code
In Visual Studio, set up your own project that includes XML comments.
(Note that the XML documentation output is not enabled by default in Visual Studio. Go to your project properties, select Build, and under the Output section check the box that says XML Documentation File. It is very important that you do not change the location of the XML file or this method will not be able to locate it at run time.)
If you have not already, add the comments in Visual Studio. By default, pressing forward slash three times when the caret is positioned before the start of an element you wish to document (such as a class or method) will insert a documentation template for you before the start of the file.
Add the DocsByReflection project to your solution and a Reference to it from your project. Add the line “using JimBlackler.DocsByReflection;” to the top of your .CS file. The call se the functions DocsByReflection.XMLFromType() (for classes, structures and other types) and DocsByReflection.XMLFromMember() (for methods, properties and fields) to fetch XmlElement objects that represent the documentation for that type or member.
Query the returned object to read the documentation. For instance, xmlElement[“summary”].InnerText.Trim(). This locates the summary node, and uses the InnerText property of XmlElement to strip out any XML formatting that may be embedded in the comment. Also, Trim() or a regular expression can strip the unwanted whitespace from the comment.
Comments or problems
If you have any comments or problems with the library, please add a reply to this blog post and I will answer them here.
-
This is great, but I’m trying to used it to get the summary information from core .NET assemblies such as System.Web. The related assemblies are “not found.”
Yet, I believe they are somewhere because you can get the summary information from intellisense or from “go to definition” command and then looking for it in the class view (from metadata).
Any ideas of how to get documentation info from core libraries?
-
Hi Jim,
I just modded your code to add the ability to specify a basepath:
private static XmlDocument XMLFromAssemblyNonCached(Assembly assembly, String basePath)
{
string assemblyFilename = assembly.CodeBase;
Regex thisResx = new Regex(@”(.*)[\/\\]([^\/\\]+\.\w+)$”, RegexOptions.IgnoreCase | RegexOptions.Multiline);
Match thisMatch = thisResx.Match(assemblyFilename);const string prefix = “file:///”;
assemblyFilename = prefix + basePath + thisMatch.Groups[2];if (assemblyFilename.StartsWith(prefix))
{
StreamReader streamReader;try
{
streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename.Substring(prefix.Length), “.xml”));
}
catch (FileNotFoundException exception)
{
throw new DocsByReflectionException(”XML documentation not present (make sure it is turned on in project properties when building)”, exception);
}XmlDocument xmlDocument = new XmlDocument();
xmlDocument.Load(streamReader);
return xmlDocument;
}
else
{
throw new DocsByReflectionException(”Could not ascertain assembly filename”, null);
}
} -
Hi Jim, this is a great work!
I got a small problem (exception), if I try to get docs for methods without args. As shown here, an XML-file-definition has no “()” brackets as long as the method has an empty ergument list:
This method should write to a file. We write to the console to see the effect this object keeps no state.
After a small improvement in XMLFromMember all works perfectly.
Here is my bug-fix:
//AL: 15.04.2008 ==> BUG-FIX remove “()” if parametersString is empty
if (parametersString.Length > 0)
return XMLFromName(methodInfo.DeclaringType, ‘M’, methodInfo.Name + “(” + parametersString + “)”);
else
return XMLFromName(methodInfo.DeclaringType, ‘M’, methodInfo.Name); -
I think you will find this quite interesting :)
http://web.archive.org/web/20070825074010/http://msdn.microsoft.com/msdnmag/issues/04/06/NETMatters/
Tip :
System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()gives “C:\Windows\Microsoft.NET\Framework\v2.0.50727″
Note : my xml files are located in the “en” subdirectory…
Best regards,
Jeremy -
HI Jim,
I’m getting an error when I say this:
MethodInfo info = Type.GetType ().GetMethod();
XmlElement documentation = DocsByReflection.XMLFromMember(info);The error is {”Unable to cast object of type ‘System.Xml.XmlComment’ to type ‘System.Xml.XmlElement’.”}
any idea what could be the reason?
NOTE: The same above statements used to work perfectly fine till a few months back, but no more. I don’t know what did I change in the project.
-
Hi Jim,
Does anyone get fix for the ”Unable to cast object of type ‘System.Xml.XmlComment’ to type ‘System.Xml.XmlElement’.” error ?
-
Hi Jim,
great stuff, works fine. Your code saved a lot of typing work in my project.
Thanks!
/Olli

12 comments
Comments feed for this article
Trackback link: http://jimblackler.net/blog/wp-trackback.php?p=49