
Contents
- Introduction
- Tags in Detail
- Namespace Tag
- Type Tag
- Field Tag
- Event Tag
- Method Tag
- Property Tag
- Using the Code
- Final Words
- References
- Revision History
This article discusses the process of generation of the XmlDocId
tag. The XmlDocId
tag and associated documentation is constructed for each type, property, event, field, delegate, and method, when a compiler generates XML documentation file for an assembly. This tag is heavily used by the documentation generators (for instance, Sandcastle or NDoc). It allows to uniquely identify an element from the documentation file (method, type, etc.) and bind it to the data gathered from Reflection.
Of course, there is already a document on MSDN describing the XmlDocId tag, but in some areas, it is out of date, incomplete, and it does not provide any code / library for generating XmlDocId
. In this article, we will discuss the rules that govern the generation of XmlDocId
tags for each element and show the code for constructing such tags.
I know that this topic might not seem very interesting, but I hope that somebody will find it useful. I only hope my small contributions will give back some of what I have taken from CodeProject.
English is not my native language, so forgive my horrible mistakes, especially the misuse of a/an/the.
In this chapter, we will discuss the tag generation rules for each element, beginning with the most simple, and ending with the most advanced. Each subsection contains a description of the tag's generation process with several examples.
In the C# compiler, you can enable documentation generation for an assembly by using the /doc switch.
The namespace tag identifies a namespace; it starts with "N:", followed by the namespace name. For instance, the XmlDocId
for the System
namespace is N:System
, and for System.Collections
is N:System.Collections
. Note that you can add documentation comments to a namespace only in the documentation generator application, but not in Visual Studio. The reason is obvious, a single namespace can be "defined" several times (and even in multiple files); if it would be possible that you could add comments to any namespace, Visual Studio, the compiler, will not be able to determine in what order it has to merge all the comments related to a single namespace. You may ask why this subsection exists if we cannot add comments to a namespace. The answer is simple, we cannot add comments to a namespace, but we can make a reference from another comment to a namespace using the cref
attribute in the <a>
tags recommended for documentation comments.
This tag identifies a class, structure, or a delegate (yes, delegate is a type!). It begins with T:
, followed by a namespace that a type resides in, and finally the name of a type with a preceding .
char, as shown in the listing below. If a type is nested inside another class or structure, the enclosing type's name must also precede the final type's name, as shown in the listing.
namespace SomeNamespace
{
public class SomeEnclosingClass
{
public class SomeNestedClass { }
}
}
If a type is generic, in addition to the namespace and type name, the number of generic parameters that type accepts is also added to the ID (preceded by the `
char), as shown in the listing here:
namespace SomeNamespace
{
public class SomeGenericClass<A, B, C> { }
}
Note that generic types that have the same name are distinguished by the number of generic parameters that type accepts; try to compile the code below, and you will get an error!
namespace SomeNamespace
{
public class SomeGenericClass<A, B> { }
public class SomeGenericClass<G, H, J> { }
public class SomeGenericClass<Z, Y> { }
}
Please remember that the underlying representation of a delegate in the CLR is a type; the XmlDocId
tag follows this pattern.
namespace SomeNamespace
{
public delegate void SomeGenericDelegate<A, B>();
}
The definition of XmlDocId
for a field is straightforward, it starts with F:
, followed by the namespace name, the type that the field is a member of, and the field name itself; of course, everything is separated by the dot char.
namespace SomeNamespace
{
public class SomeClass
{
public int SomeField;
}
}
If the enclosing type is generic, the number of generic parameters must also be given.
namespace SomeNamespace
{
public class SomeGenericClass<A>
{
public int SomeField;
}
}
The process of generation of tags for events is very similar to generating tags for a field, except that an XmlDocId
for an event starts with E:
, as shown in the code below:
namespace SomeNamespace
{
public class SomeGenericClass<A>
{
public delegate void SomeDelegate(int Param1);
public event SomeDelegate SomeEvent;
}
}
The method tag is generated for a constructor, operator, property's getter/setter, and of course... a method. When compared to the previously mentioned tags, the method XmlDocId
may look a little bit more harder, but it is deceptive; in fact, the method tag is much more complex.
The XmlDocId
starts with M:
, followed by the namespace that method's enclosing type resides in, period, type name, period, method name, and fully qualified names of parameters that the method accepts inside a parenthesis, separated by comma. If a method is parameterless, the parenthesis is not omitted. Of course, there are no whitespaces in the generated ID; see sample below:
namespace SomeNamespace
{
public class SomeClass
{
public void SomeMethod(int Param1, string Param2) { }
public void SomeParameterlessMethod() { }
}
}
If the method's name contains a period, it is preceded by the hash sign (#
). For instance, XmlDocId
s for a constructor (.ctor
) and a static
constructor (.cctor
) are #.ctor
and #.cctor
, respectively, as depicted in the code listing below:
namespace SomeNamespace
{
public class SomeClass
{
public SomeClass() { }
static SomeClass() { }
}
}
A method can also have generic parameters; such a method's name ends with ``
and is followed by the number of generic parameters the method has. Please note that a generic method can be enclosed in a generic type; in that case, the number of parameters of the generic type is also appended to the type name, preceded by the `
character, as can be seen in the sample code below:
namespace SomeNamespace
{
public class SomeClass
{
public void SomeMethod() { }
}
public class SomeGenericClass<A>
{
public void SomeGenericMethod<B>() { }
}
}
You might ask, but how is the XmlDocId
generated for a method's parameter whose type is a generic parameter of either the enclosing type or the method? The generic parameter inside the parenthesis is referenced by using the `%n
format for referencing a type's generic parameter and the ``%n
format for referencing a method's generic parameter, where %n
is a zero-based index of a generic parameter, as you can see in the code listing below:
namespace SomeNamespace
{
public class SomeGenericClass<A, B, C>
{
public void SomeGenericMethod<D, E>(B TypesParam, D MethodsParam) { }
}
}
How are generic parameters in a method's parameters list referenced when a method is a member of a generic nested class whose enclosing class is also generic? To call a method which is the member of a nested generic class, you have to supply the types of generic parameters to both the enclosing and the nested class. The compiler considers that the nested type "inherits" the generic parameters from its enclosing types; hence, the SomeNestedGenericClass
class has not only the D
and E
parameters, but also A
, B
, and C
; as a result, the total number of parameters is 5. The index of a parameter in the `%n
format is relative to the most nested type; see code below:
namespace SomeNamespace
{
public class SomeGenericClass<A, B, C>
{
public class SomeNestedGenericClass<D, E>
{
public void SomeMethod(A Param1, E Param2) { }
}
}
}
It is also possible that a method's normal parameter is a generic type (not a generic parameter!) whose generic parameter references a method's or type's generic parameter. In this case, such a generic parameter is referenced by the {``%n}
and {`%n}
formats for the generic method's parameter and the generic type's parameter, respectively. This may seem unclear, so let us look at some sample code:
using System;
using System.Collections.Generic;
namespace SomeNamespace
{
public class SomeGenericClass<A, B>
{
public void SomeGenericMethod<C, D>(List<A> Param1, List<D> Param2) { }
public void SomeGenericNestedMethod<C, D>(List<List<A>> Param1) { }
}
}
If a method is an explicit interface implementation, before a method name, a fully qualified name of the implemented member is put (i.e., the namespace of the interface, the enclosing interface's ID, and the member name). Unfortunately, since .NET3.5/VS2008, the XmlDocId
for explicitly implemented members has changed a little bit: the key difference is that in the part of the ID that specifies the location of a member in an interface, some special characters are different; for example, the period (.
) is replaced by the hash-sign (#
). There are much more "special characters", but we will talk about them along the way in the next paragraph. Now, let us see some sample code:
namespace SomeNamespace
{
public interface SomeInterface
{
void SomeMethod();
}
public class SomeClass : SomeInterface
{
void SomeInterface.SomeMethod() { }
}
}
As I promised, we will now discuss XmlDocId
s for a method's parameters. Shown in the table below are all the modifiers that can be added to a method's parameters, such as pointer, reference, etc., along with the corresponding XmlDocId
s that shall be added to a method tag.
Modifier
| C# syntax
| Added XmlDocId
|
Pointer
| *
| *
|
Reference
| ref
out
| @
|
Single-dimensional array with zero lower bounds
| []
| []
|
Multi-dimensional array
| [,]
| [lowerbound:size,lowerbound:size]
Where lowerbound and size are the lower boundary and the size of the specified dimension, respectively. If the lower boundary or the size of a dimension is not specified, it is omitted.
|
Pinned
| -
| ^
The C# compiler never generates this modifier.
|
Required modifier
| -
| |
The C# compiler never generates this modifier.
|
Optional modifier
| -
| !
The C# compiler never generates this modifier.
|
Function pointer
| -
| =FUNC:type(signature)
Where type stands for the return type, and signature parameters that can be supplied to a method. The C# compiler never generates this modifier.
|
To make things more clear, see the sample code below:
namespace SomeNamespace
{
public class SomeClass
{
unsafe void SomeMethod(int* Param1, int[, ,] Param2, ref string Param3,
out string Param4) { Param4 = string.Empty; }
}
}
Let us get back to generating the XmlDocId
tag for explicitly implemented members for a while, as you remember that an interface can also have generic parameters. Thus, when generating a tag for an explicitly implemented member that resides in a generic interface, it has to reference the generic parameters somehow, and it does it like in the example above. However, since .NET3.5/VS2008, some previously mentioned "special characters" are different in the part that describes the location of the implemented member in an interface. As you already know, .
goes into #
, the period ,
goes into @
, the multi-dimensional array with zero lower bound and unspecified size [0:,0:]
goes into [@]
, and the separator of generic parameters ,
goes into @
.
namespace SomeNamespace
{
public interface SomeInterface<A, B>
{
void SomeExplicitMethod();
}
unsafe public class SomeClass : SomeInterface<int*[, ,][], string>
{
unsafe void SomeInterface<int*[, ,][], string>.SomeExplicitMethod() { }
}
}
But we are not done with explicitly implemented members yet, what will happen if we explicitly implement some member that has generic declaring type (for instance IEnumerable<T>.GetEnumerator()) ? How will its XmlDocId
look like? One would expect that if we explicitly implement the mentioned method in SomeNamespace.SomeClass<T>
class, its XmlDocId
will be like this M:SomeNamespace.SomeClass`1.System#Collections#Generic#IEnumerable{`1}#GetEnumerator
, in fact it is M:SomeNamespace.SomeClass`1.System#Collections#Generic#IEnumerable{T}#GetEnumerator
. Yes, in part of XmlDocId
describing location of explicitly implemented member the generic parameter name is used instead of generic parameter order. This difference is also true for older .NET Framework and Visual Studio versions - see below sample.
namespace SomeNamespace
{
public interface SomeInterface<T>
{
void SomeMethod();
}
public class SomeClass<T> : SomeInterface<T>
{
void SomeInterface<T>.SomeMethod() { }
}
}
As you probably know, a method's return type is not used to differentiate methods, and thus is not part of XmlDocId
, but there is an exception to this rule. When a method is either an explicit or implicit conversion operator. In this case, in addition to the standard XmlDocId
for the method, the ~%s
is appended, where %s
stands for the conversion operator's return type. Note that the method name for the explicit conversion operator is op_Explicit
, and for the implicit conversion operator is op_Implicit
.
namespace SomeNamespace
{
public class SomeClass
{
public static explicit operator int(SomeClass Param1) { return 0; }
public static implicit operator string(SomeClass Param1)
{ return string.Empty; }
}
}
The full list of the method's names for operators is presented below as an enumeration:
public enum OperatorType
{
None,
op_Implicit,
op_Explicit,
op_Decrement,
op_Increment,
op_UnaryNegation,
op_UnaryPlus,
op_LogicalNot,
op_True,
op_False,
op_OnesComplement,
op_Like,
op_Addition,
op_Subtraction,
op_Division,
op_Multiply,
op_Modulus,
op_BitwiseAnd,
op_ExclusiveOr,
op_LeftShift,
op_RightShift,
op_BitwiseOr,
op_Equality,
op_Inequality,
op_LessThanOrEqual,
op_LessThan,
op_GreaterThanOrEqual,
op_GreaterThan,
op_AddressOf,
op_PointerDereference,
op_LogicalAnd,
op_LogicalOr,
op_Assign,
op_SignedRightShift,
op_UnsignedRightShift,
op_UnsignedRightShiftAssignment,
op_MemberSelection,
op_RightShiftAssignment,
op_MultiplicationAssignment,
op_PointerToMemberSelection,
op_SubtractionAssignment,
op_ExclusiveOrAssignment,
op_LeftShiftAssignment,
op_ModulusAssignment,
op_AdditionAssignment,
op_BitwiseAndAssignment,
op_BitwiseOrAssignment,
op_Comma,
op_DivisionAssignment
}
Remember that only in explicit and implicit conversions, the operator's return type is used to distinguish the operator overloads, so operators such as addition, subtraction, or decrement does not have the ~%s
in their XmlDocId
s. You can see the difference in the sample code below:
namespace SomeNamespace
{
public class SomeClass
{
public static int operator +(SomeClass Param1, SomeClass Param2) { return 0; }
public static string operator -(SomeClass Param1,
SomeClass Param2) { return string.Empty; }
public static explicit operator int(SomeClass Param1) { return 0; }
}
}
The XmlDocId
for properties is generated in the same manner as for methods, with some exceptions. The first thing is that the tag for a property starts with P:
; the second difference is that a property cannot act as a conversion operator, so there is no need to put the property's return type ID into the resultant XmlDocId
. In contrast to methods, properties cannot have any generic parameters, thus the ``%n
format is not used here. Please note that the tag being discussed in the current subsection is generated for a property, not for its getter or setter method. We will not be repeating here the procedure for generating a method's XmlDocId
; instead, we will show several examples. Let us first see this one:
namespace SomeNamespace
{
public class SomeClass
{
public int SomeProperty { get; set; }
public int this[int Param1]
{
get { return 0; }
set { }
}
}
}
The property's enclosing type is a generic type.
namespace SomeNamespace
{
public class SomeGenericClass<A>
{
public int SomeProperty { get; set; }
}
}
Referencing a generic parameter from a property's parameter list:
namespace SomeNamespace
{
public class SomeGenericClass<A, B, C>
{
public int this[B Param1, C Param2]
{
get { return 0; }
set { }
}
}
}
Explicit interface
implementation of a property:
namespace SomeNamespace
{
public interface SomeInterface
{
int SomeProperty { get; set; }
}
public class SomeGenericClass : SomeInterface
{
int SomeInterface.SomeProperty { get; set; }
}
}
Explicit interface
implementation of a property which is a member of a generic interface:
namespace SomeNamespace
{
public interface SomeInterface<A, B>
{
int SomeProperty { get; set; }
}
unsafe class SomeGenericClass : SomeInterface<int*[][, ,], string>
{
unsafe int SomeInterface<int*[][, ,], string>.SomeProperty { get; set; }
}
}
You can see how the XmlDocIdLib
works in action by running the sample GUI application that can be downloaded at the beginning of the article. Run it, load an assembly by clicking at the add button ('+' icon), choose the desired compatibility mode; now, as you click any tree's element, the XmlDocId
tag will be generated for the selected element.
If you want to generate XmlDocId
s in your code, the first thing you have to do is to initialize an instance of the XmlDocIdGenerator
class that resides in the XmlDocIdLib
namespace. Next, you may set the compatibility type using the SetCompatibilityType
method. Finally, you can generate a tag using the GetXmlDocPath
method, supplying as argument type, an event, method, field, or property. To demonstrate how to use the XmlDocIdLib
API, I have written the following console application that lists all types, methods, properties, fields that are in an assembly, and creates XmlDocId
s for them:
using System;
using Mono.Cecil;
using Mono.Cecil.Binary;
using XmlDocIdLib;
namespace SampleApp
{
public class Program
{
public static void Main(string []args)
{
XmlDocIdGenerator idGenerator = new XmlDocIdGenerator();
idGenerator.SetCompatibilityType(CompatibilityType.Net35);
foreach(string tempArg in args)
ListAssemblyIds(tempArg, idGenerator);
}
public static void ListAssemblyIds(
string AssemblyFile,
XmlDocIdGenerator Generator)
{
AssemblyDefinition assemblyDef = null;
try
{
assemblyDef = AssemblyFactory.GetAssembly(AssemblyFile);
}
catch(ImageFormatException)
{
Console.WriteLine("Supplied assembly \"" +
AssemblyFile + "\" is not valid .NET file.");
return;
}
catch(Exception generalEx)
{
Console.WriteLine("An error occurred while opening a file: " +
generalEx.Message);
return;
}
Console.WriteLine("Assembly: " + assemblyDef.Name.ToString());
foreach(ModuleDefinition tempModuleDef in assemblyDef.Modules)
{
Console.WriteLine("Module: " + tempModuleDef.Name);
foreach(TypeDefinition tempTypeDef in tempModuleDef.Types)
{
Console.WriteLine("Type: " + tempTypeDef.ToString() +
" | " + Generator.GetXmlDocPath(tempTypeDef));
foreach (MethodDefinition tempMethodDef in tempTypeDef.Methods)
Console.WriteLine("Method: " + tempMethodDef.ToString() +
" | " + Generator.GetXmlDocPath(tempMethodDef));
foreach (PropertyDefinition tempPropertyDef in tempTypeDef.Properties)
Console.WriteLine("Property: " + tempPropertyDef.ToString() +
" | " + Generator.GetXmlDocPath(tempPropertyDef));
foreach (FieldDefinition tempFieldDef in tempTypeDef.Fields)
Console.WriteLine("Field: " + tempFieldDef.ToString() +
" | " + Generator.GetXmlDocPath(tempFieldDef));
foreach (EventDefinition tempEventDef in tempTypeDef.Events)
Console.WriteLine("Event: " + tempEventDef.ToString() +
" | " + Generator.GetXmlDocPath(tempEventDef));
}
}
}
}
}
I have determined all the mentioned rules of generation of XmlDocId
s using the specification: MSDN's MTPS Web Service, and partially by 'trial and error' methods. So when implementing XmlLibId
in your program, remember that I may be wrong; if you find any mistake(s), please let me know.
- 1.0: 30th September, 2009
- 1.1: 19th April 2010
- Added section about explicitly implemented members in generic declaring types
- Updated downloads