Click here to Skip to main content
15,867,851 members
Articles / Programming Languages / C#

Generating XmlDocId using Mono.Cecil

Rate me:
Please Sign up or sign in to vote.
4.73/5 (6 votes)
19 Apr 2010CPOL12 min read 31.4K   288   17   6
The article describes the rules of generation of the XmlDocId tag that is used by the .NET Framework to uniquely identify a namespace, property, event, delegate, class, or structure.

XmlDocId Generator application

Contents

  1. Introduction
  2. Tags in Detail
    1. Namespace Tag
    2. Type Tag
    3. Field Tag
    4. Event Tag
    5. Method Tag
    6. Property Tag
  3. Using the Code
  4. Final Words
  5. References
  6. Revision History

1. Introduction

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.

2. Tags in Detail

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.

2.1 Namespace Tag

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.

2.2 Type Tag

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.

C#
namespace SomeNamespace
{
    // <summary>
    // Generated XmlDocId is: T:SomeNamespace:SomeEnclosingClass
    // </summary>

    public class SomeEnclosingClass
    {
        /// <summary>
        /// Generated XmlDocId is: T:SomeNamespace.SomeEnclosingClass.SomeNestedClass
        /// </summary>
        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:

C#
namespace SomeNamespace
{
    /// <summary>
    /// Generated XmlDocId is: T:SomeNamespace.SomeGenericClass`3
    /// </summary>
    /// <typeparam name="A"></typeparam>
    /// <typeparam name="B"></typeparam>

    /// <typeparam name="C"></typeparam>
    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!

C#
namespace SomeNamespace
{
    public class SomeGenericClass<A, B> { }

    // OK !
    public class SomeGenericClass<G, H, J> { }

    // Generates error: CS0101
    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.

C#
namespace SomeNamespace
{
    /// <summary>

    /// Generated XmlDocId is: T:SomeNamespace.SomeGenericDelegate`2
    /// </summary>
    /// <typeparam name="A"></typeparam>
    /// <typeparam name="B"></typeparam>
    public delegate void SomeGenericDelegate<A, B>();
}

2.3 Field Tag

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.

C#
namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is: F:SomeNamespace.SomeClass.SomeField
        /// </summary>
        public int SomeField;
    }
}

If the enclosing type is generic, the number of generic parameters must also be given.

C#
namespace SomeNamespace
{
    public class SomeGenericClass<A>
    {
        /// <summary>
        /// Generated XmlDocId is: F:SomeNamespace.SomeGenericClass`1.SomeField
        /// </summary>
        public int SomeField;
    }
}

2.4 Event Tag

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:

C#
namespace SomeNamespace
{
    public class SomeGenericClass<A>
    {
        public delegate void SomeDelegate(int Param1);

        /// <summary>
        /// Generated XmlDocId is: E:SomeNamespace.SomeGenericClass`1.SomeEvent
        /// </summary>

        public event SomeDelegate SomeEvent;
    }
}

2.5 Method Tag

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:

C#
namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is:
        ///   M:SomeNamespace.SomeClass.SomeMethod(System.Int32,System.String)
        /// </summary>
        /// <param name="Param1"></param>
        /// <param name="Param2"></param>
        public void SomeMethod(int Param1, string Param2) { }

        /// <summary>

        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.SomeParameterlessMethod
        /// </summary>
        public void SomeParameterlessMethod() { }
    }
}

If the method's name contains a period, it is preceded by the hash sign (#). For instance, XmlDocIds for a constructor (.ctor) and a static constructor (.cctor) are #.ctor and #.cctor, respectively, as depicted in the code listing below:

C#
namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.#ctor
        /// </summary>
        public SomeClass() { }

        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.#cctor
        /// </summary>
        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:

C#
namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.SomeMethod
        /// </summary>

        public void SomeMethod() { }
    }

    public class SomeGenericClass<A>
    {
        /// <summary>
        /// Generated XmlDocId is: 
        /// M:SomeNamespace.SomeGenericClass`1.SomeGenericMethod``1
        /// </summary>
        /// <typeparam name="B"></typeparam>

        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:

C#
namespace SomeNamespace
{
    public class SomeGenericClass<A, B, C>
    {
        /// <summary>
        /// Generated XmlDocId is:
        ///   M:SomeNamespace.SomeGenericClass`3.SomeGenericMethod``2(`1,``0)
        /// </summary>
        /// <typeparam name="D"></typeparam>

        /// <typeparam name="E"></typeparam>
        /// <param name="TypesParam"></param>
        /// <param name="MethodsParam"></param>

        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:

C#
namespace SomeNamespace
{
    public class SomeGenericClass<A, B, C>
    {
        public class SomeNestedGenericClass<D, E>
        {
            /// <summary>
            /// Generated XmlDocId is:
            ///   M:SomeNamespace.SomeGenericClass`3.
            ///   SomeNestedGenericClass`2.SomeMethod(`0,`4)
            /// </summary>
            /// <param name="Param1"></param>

            /// <param name="Param2"></param>
            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:

C#
using System;
using System.Collections.Generic;

namespace SomeNamespace
{
    public class SomeGenericClass<A, B>
    {
        /// <summary>
        /// Generated XmlDocId is:
        ///    M:SomeNamespace.SomeGenericClass`2.
        ///    SomeGenericMethod``2(System.Collections.
        ///    Generic.List{`0},System.Collections.Generic.List{``1})
        /// </summary>
        /// <typeparam name="C"></typeparam>

        /// <typeparam name="D"></typeparam>
        /// <param name="Param1"></param>
        /// <param name="Param2"></param>

        public void SomeGenericMethod<C, D>(List<A> Param1, List<D> Param2) { }

        /// <summary>
        /// Generated XmlDocId is:
        ///     M:SomeNamespace.SomeGenericClass`2.
        ///     SomeGenericNestedMethod``2(System.Collections.
        ///     Generic.List{System.Collections.Generic.List{`0}})
        /// </summary>
        /// <typeparam name="C"></typeparam>

        /// <typeparam name="D"></typeparam>
        /// <param name="Param1"></param>
        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:

C#
namespace SomeNamespace
{
    public interface SomeInterface
    {
        void SomeMethod();
    }

    public class SomeClass : SomeInterface
    {
        /// <summary>
        /// Generated XmlDocId for >= .NET3.5/VS2008 is:
        ///    M:SomeNamespace.SomeClass.SomeNamespace#SomeInterface#SomeMethod
        /// Generated XmlDocId for < .NET3.5/VS2008 is:
        ///    M:SomeNamespace.SomeClass.SomeNamespace.SomeInterface.SomeMethod
        /// </summary>

        void SomeInterface.SomeMethod() { }
    }
}

As I promised, we will now discuss XmlDocIds 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 XmlDocIds 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:

C#
// compile with "/unsafe" switch

namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.SomeMethod(
        //     System.Int32*,System.Int32[0:,0:,0:],System.String@,System.String@)
        /// </summary>

        /// <param name="Param1"></param>
        /// <param name="Param2"></param>
        /// <param name="Param3"></param>

        /// <param name="Param4"></param>
        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 @.

C#
// compile with "/unsafe" switch

namespace SomeNamespace
{
    public interface SomeInterface<A, B>
    {
        void SomeExplicitMethod();
    }

    unsafe public class SomeClass : SomeInterface<int*[, ,][], string>
    {
        /// <summary>
        /// Generated XmlDocId >= .NET3.5/VS2008 is:
        ///    M:SomeNamespace.SomeClass.SomeNamespace#SomeInterface{
        ///    System#Int32*[][@@]@System#String}#SomeExplicitMethod
        /// Generated XmlDocId < .NET3.5/VS2008 is:
        ///    M:SomeNamespace.SomeClass.SomeNamespace.SomeInterface{
        ///    System.Int32*[0:,0:,0:][],System.String}.SomeExplicitMethod
        /// </summary>

        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.

C#
namespace SomeNamespace
{
    public interface SomeInterface<T>
    {
        void SomeMethod();
    }

    public class SomeClass<T> : SomeInterface<T>
    {
        /// <summary>
        /// Generated XmlDocId  >= .NET3.5/VS2008 is: 
        ///     M:SomeNamespace.SomeClass`1.
        ///     SomeNamespace#SomeInterface{T}#SomeMethod
        /// Generated XmlDocId < .NET3.5/VS2008 is:
        ///     M:SomeNamespace.SomeClass`1.
        ///     SomeNamespace.SomeInterface{T}.SomeMethod
        /// </summary>
        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.

C#
namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.
        ///      op_Explicit(SomeNamespace.SomeClass)~System.Int32
        /// </summary>
        /// <param name="Param1"></param>
        /// <returns></returns>

        public static explicit operator int(SomeClass Param1) { return 0; }

        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.
        ///    SomeClass.op_Implicit(SomeNamespace.SomeClass)~System.String
        /// </summary>
        /// <param name="Param1"></param>
        /// <returns></returns>

        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:

C#
public enum OperatorType
{
    None,
    op_Implicit,
    op_Explicit,

    // overloadable unary operators
    op_Decrement,                   // --
    op_Increment,                   // ++
    op_UnaryNegation,               // -
    op_UnaryPlus,                   // +
    op_LogicalNot,                  // !
    op_True,                        // true
    op_False,                       // false
    op_OnesComplement,              // ~
    op_Like,                        // Like (Visual Basic)


    // overloadable binary operators
    op_Addition,                    // +
    op_Subtraction,                 // -
    op_Division,                    // /
    op_Multiply,                    // *
    op_Modulus,                     // %
    op_BitwiseAnd,                  // &
    op_ExclusiveOr,                 // ^
    op_LeftShift,                   // <<
    op_RightShift,                  // >>
    op_BitwiseOr,                   // |

    // overloadable comparison operators
    op_Equality,                    // ==
    op_Inequality,                  // != 
    op_LessThanOrEqual,             // <=
    op_LessThan,                    // <

    op_GreaterThanOrEqual,          // >=
    op_GreaterThan,                 // >

    // not overloadable operators
    op_AddressOf,                       // &
    op_PointerDereference,              // *
    op_LogicalAnd,                      // &&
    op_LogicalOr,                       // ||
    op_Assign,                          // Not defined (= is not the same)
    op_SignedRightShift,                // Not defined
    op_UnsignedRightShift,              // Not defined
    op_UnsignedRightShiftAssignment,    // Not defined
    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 XmlDocIds. You can see the difference in the sample code below:

C#
namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is:M:SomeNamespace.SomeClass.
        ///   op_Addition(SomeNamespace.SomeClass,SomeNamespace.SomeClass)
        /// </summary>
        /// <param name="Param1"></param>
        /// <param name="Param2"></param>

        /// <returns></returns>
        public static int operator +(SomeClass Param1, SomeClass Param2) { return 0; }

        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.
        ///    op_Subtraction(SomeNamespace.SomeClass,SomeNamespace.SomeClass)
        /// </summary>
        /// <param name="Param1"></param>

        /// <param name="Param2"></param>
        /// <returns></returns>
        public static string operator -(SomeClass Param1, 
                      SomeClass Param2) { return string.Empty; }

        /// <summary>
        /// Generated XmlDocId is: M:SomeNamespace.SomeClass.
        ///    op_Explicit(SomeNamespace.SomeClass)~System.Int32
        /// </summary>

        /// <param name="Param1"></param>
        /// <returns></returns>
        public static explicit operator int(SomeClass Param1) { return 0; }
    }
}

2.6 Property Tag

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:

C#
namespace SomeNamespace
{
    public class SomeClass
    {
        /// <summary>
        /// Generated XmlDocId is: P:SomeNamespace.SomeClass.SomeProperty
        /// </summary>
        public int SomeProperty { get; set; }

        /// <summary>
        /// Generated XmlDocId is: P:SomeNamespace.SomeClass.Item(System.Int32)
        /// </summary>
        /// <param name="Param1"></param>

        /// <returns></returns>
        public int this[int Param1]
        {
            get { return 0; }
            set { }
        }
    }
}

The property's enclosing type is a generic type.

C#
namespace SomeNamespace
{
    public class SomeGenericClass<A>
    {
        /// <summary>

        /// Generated XmlDocId is:
        ///     P:SomeNamespace.SomeGenericClass`1.SomeProperty
        /// </summary>
        public int SomeProperty { get; set; }
    }
}

Referencing a generic parameter from a property's parameter list:

C#
namespace SomeNamespace
{
    public class SomeGenericClass<A, B, C>
    {
        /// <summary>

        /// Generated XmlDocId is: P:SomeNamespace.SomeGenericClass`3.Item(`1,`2)
        /// </summary>
        public int this[B Param1, C Param2]
        {
            get { return 0; }
            set { }
        }
    }
}

Explicit interface implementation of a property:

C#
namespace SomeNamespace
{
    public interface SomeInterface
    {
        int SomeProperty { get; set; }
    }

    public class SomeGenericClass : SomeInterface
    {
        /// <summary>
        /// Generated XmlDocId >= .NET3.5/VS2008 is:
        ///    P:SomeNamespace.SomeGenericClass.
        ///    SomeNamespace#SomeInterface#SomeProperty
        /// Generated XmlDocId < .NET3.5/VS2008 is:
        ///    P:SomeNamespace.SomeGenericClass.
        ///    SomeNamespace.SomeInterface.SomeProperty
        /// </summary>

        int SomeInterface.SomeProperty { get; set; }
    }
}

Explicit interface implementation of a property which is a member of a generic interface:

C#
// compile with "/unsafe" switch

namespace SomeNamespace
{
    public interface SomeInterface<A, B>
    {
        int SomeProperty { get; set; }
    }

    unsafe class SomeGenericClass : SomeInterface<int*[][, ,], string>

    {
        /// <summary>
        /// Generated XmlDocId >= .NET3.5/VS2008 is:
        ///     P:SomeNamespace.SomeGenericClass.
        ///     SomeNamespace#SomeInterface{
        ///     System#Int32*[@@][]@System#String}#SomeProperty
        /// Generated XmlDocId < .NET3.5/VS2008 is:
        ///     P:SomeNamespace.SomeGenericClass.SomeNamespace.
        ////    SomeInterface{System.Int32*[,,][],System.String}.SomeProperty
        /// </summary>
        unsafe int SomeInterface<int*[][, ,], string>.SomeProperty { get; set; }
    }
}

3. Using the Code

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 XmlDocIds 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 XmlDocIds for them:

C#
// to compile, issue the following command:
// csc /r:Mono.Cecil.dll /r:xmlDocIdLib.dll TestConsoleApp.cs

using System;

using Mono.Cecil;
using Mono.Cecil.Binary;

using XmlDocIdLib;

namespace SampleApp
{
    public class Program
    {
        public static void Main(string []args)
        {
            // initialize ID generator
            XmlDocIdGenerator idGenerator = new XmlDocIdGenerator();

            // set compatibility mode to .NET3.5/VS2008
            idGenerator.SetCompatibilityType(CompatibilityType.Net35);

            foreach(string tempArg in args)
                ListAssemblyIds(tempArg, idGenerator);
        }

        public static void ListAssemblyIds(
            string AssemblyFile,
            XmlDocIdGenerator Generator)
        {
            // try open an assembly
            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;
            }

            // show current assembly full name
            Console.WriteLine("Assembly: " + assemblyDef.Name.ToString());

            // let's go
            foreach(ModuleDefinition tempModuleDef in assemblyDef.Modules)
            {
                // show current module name
                Console.WriteLine("Module: " + tempModuleDef.Name);

                foreach(TypeDefinition tempTypeDef in tempModuleDef.Types)
                {
                    // show name and XmlDocId for current type
                    Console.WriteLine("Type: " + tempTypeDef.ToString() + 
                      " | " + Generator.GetXmlDocPath(tempTypeDef));

                    // list all methods
                    foreach (MethodDefinition tempMethodDef in tempTypeDef.Methods)
                        Console.WriteLine("Method: " + tempMethodDef.ToString() + 
                          " | " + Generator.GetXmlDocPath(tempMethodDef));

                    // list all properties
                    foreach (PropertyDefinition tempPropertyDef in tempTypeDef.Properties)
                        Console.WriteLine("Property: " + tempPropertyDef.ToString() + 
                          " | " + Generator.GetXmlDocPath(tempPropertyDef));

                    // list all fields
                    foreach (FieldDefinition tempFieldDef in tempTypeDef.Fields)
                        Console.WriteLine("Field: " + tempFieldDef.ToString() + 
                          " | " + Generator.GetXmlDocPath(tempFieldDef));

                    // list all events
                    foreach (EventDefinition tempEventDef in tempTypeDef.Events)
                        Console.WriteLine("Event: " + tempEventDef.ToString() + 
                          " | " + Generator.GetXmlDocPath(tempEventDef));
                }
            }
        }
    }
}

4. Final Words

I have determined all the mentioned rules of generation of XmlDocIds 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.

5. References

6. Revision History

  • 1.0: 30th September, 2009
    • Initial release
  • 1.1: 19th April 2010
    • Added section about explicitly implemented members in generic declaring types
    • Updated downloads

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Poland Poland
Przemek was born in 1988, he lives in small town near Warsaw in Poland, Europe. Currently he codes some C# stuff and J2EE as well, ocasionally he uses C++ for fun. Przemek is cycling fun, if weather permits he rides a bike.

Comments and Discussions

 
PraiseThanks & feedback Pin
Cuft15-Jun-17 1:27
Cuft15-Jun-17 1:27 
GeneralNice work Pin
Paul Selormey1-Oct-09 19:29
Paul Selormey1-Oct-09 19:29 
This is a well thought tool, and nicely implemented - thanks.

I will check if it will be relevant in my Sandcastle Assist project.

Best regards,
Paul.


Jesus Christ is LOVE! Please tell somebody.

GeneralRe: Nice work Pin
Przemyslaw Celej1-Oct-09 20:49
Przemyslaw Celej1-Oct-09 20:49 
GeneralRe: Nice work Pin
Paul Selormey2-Oct-09 0:31
Paul Selormey2-Oct-09 0:31 
GeneralRe: Nice work Pin
Przemyslaw Celej5-Oct-09 18:04
Przemyslaw Celej5-Oct-09 18:04 
GeneralRe: Nice work Pin
Paul Selormey5-Oct-09 18:23
Paul Selormey5-Oct-09 18:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.