Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
Markdown Monster - The Markdown Editor for Windows

Generics, Reflection and XML Documentation == Madness


:P
On this page:

I’m reviewing the code I have for importing generic types into Html Help Builder today and I realized that, well – uh, I did a crappy job the first round of supporting all the different ways that Generic types can creep into type signatures. So I spent a few hours cleaning up some of the basic stuff earlier.

 

Reflection and Generics is - uh, not intuitive. Understandably so I suppose - Generics is really a type hack grafted ontop of the CLR, so it's to be expected that being able to retrieve this Generic type information also is not going to be quite as straight forward as regular Reflection. However, once you figure out the basic concepts it's actually not too bad. Getting there though might take a little bit <g>... Here are some (not overly organized) notes I took while going through this.

 

One of the things that I needed to do frequently is fix up a type description for a generic type. Generic types retrieved via Reflection have names that look like this:

 

Westwind.BusinessObjects.wwBusiness`2

 

and for display you really want the full syntax that looks like this:

 

Westwind.BusinessObject.wwBusiness<EntityType,ListType>

 

Here’s a routine that accomplishes that task:

 

/// <summary>

/// Fixes up a Generic Type name so that it displays properly for output.

/// Fixes output from

/// wwBusiness`1 to wwBusiness<EntityType>

/// </summary>

/// <param name="loType"></param>

/// <param name="ReturnFullTypeName"></param>

/// <returns></returns>

public static string GetGenericTypeName(Type GenericType, bool ReturnFullTypeName)

{

    // *** Make sure the type is indeed generic in which case the` is in the name

    int Index = GenericType.Name.IndexOf("`");

    if (Index == -1)

    {

        if(!ReturnFullTypeName)

            return GenericType.Name;

 

        return GenericType.Namespace + "." + GenericType.Name;

    }

 

    // *** Strip off the Genric postfix

    string TypeName = GenericType.Name.Substring(0,Index);

    string FormattedName = TypeName;

 

    // *** Parse the generic type arguments

    Type[] GenericArguments = GenericType.GetGenericArguments();

    string GenericOutput = "<";

    bool Start = true;

    foreach (Type GenericArg in GenericArguments)

    {

        if (Start)

        {

            GenericOutput += GenericArg.Name;

            Start = false;

        }

        else

            GenericOutput += "," + GenericArg.Name;

    }

 

    GenericOutput += ">";

    FormattedName += GenericOutput;

 

    if (!ReturnFullTypeName)

        return FormattedName;

 

    // *** Add the full namespace

    return GenericType.Namespace + "." + FormattedName;

}

 

You can basically parse through each of the generic type ‘parameters’ and pick up their names and if you need to type information.

 

This routine is handy if for nothing else than debugging, but of course in Html Help Builder it gets called all over the place for formatting generic formatted types, parameters and return values.

 

That part was easy, but the real tricky part is creating an accurate type signature. Html Help Builder uses type signatures to uniquely identify a type and match it up for imports and exports. .NET also uses type signatures to map types to entries in the XML Documentation files that are generated. I’ve been trying to match that type signature format so it’s easy to lookups into the XML documentation and pull out descriptions that way. This is trickier than it sounds, but this has been working all along with Html Help Builder since Version 3.0.

 

Now Generics make this process a whole lot more complicated. To start with it looks like there are a few holes in Reflection support for Generics. Specifically I can’t seem to get type information on Generic Parameters. For example:

 

Type ParmType = loParameters[y].ParameterType;

 

returns null on a Generic type in the debugger. But it’s not actually null – the problem is that the FullName property is null and the debugger is using FullName as it’s debugger visualization value apparently so it appears that the object is null when it really isn’t. Talk about confusing!!!

 

To get the type name information you have to use:

 

ParameterType.Namespace + "." + ParameterType.Name;

 

This still doesn’t give you a complete picture of the parameter, because – oh boy, the parameter can be either a Generic Type (#1 ie. a direct type that is generic) or a type that itself has a Generic Parameter List (#2).

 

For example here’s the #1 case:

 

public string Test(EntityType Entity, T TestValue)

 

I need to turn that into:

 

Westwind.BusinessObjects.wwBusinessGeneric`2.Test(`0,`1)

 

 

Here we have Generic Types passed as parameters.

 

The other alternative is case #2 where you have a parameter includes a Generic Parameter list. This one looks real fun, huh?

 

public List<EntityType> GetDetailList2(DataTable dt,List<EntityType> TList, List<int> Liste)

 

and it needs to turn into:

 

Westwind.BusinessObjects.wwBusinessGeneric`2.GetDetailList(System.Data.DataTable,System.Collections.Generic.List{`0},System.Collections.Generic.List{System.Int32})

 

Here the parameters are themselves Generic parameterized objects or collections.

 

To deal with these scenarios I have to have check code like this:

 

string ParmTypeName = null;

if (ParameterType.IsGenericType)

    ParmTypeName = DotnetObject.GetGenericTypeNameForSignature(ParameterType);

else if (ParameterType.IsGenericParameter)

    ParmTypeName = "`" + ParameterType.GenericParameterPosition.ToString();

else

    ParmTypeName = ParameterType.Namespace + "." + ParameterType.Name;

 

If the parameter itself is a Generic type (case #2) I’m going to have to parse the type and get it into the proper XML doc lookup format which is similar to the code I posted above for the Generic display value.

 

If the parameter is a Generic parameter the type itself is a generic type and in this case I can simply write the GenericParameterPosition which is the value you see in the XML documentation signatures.

 

Finally if it’s not generic at all you can just use the Namespace + Name. Not that in most cases FullName returns this value as well, but beware that Fullname on signed assembly types can return the sign information, so here I’ll want to make sure to always use namespace + name for the type used.

 

Finally here’s the routine that returns a type signature that works with the XML Docs format. As mentioned this code is similar to the descriptive display, but it’s different enough to require a new method.

 

public static string GetGenericTypeNameForSignature(Type GenericType)

{

    string TypeName = GenericType.Namespace + "." + GenericType.Name;

 

    // *** Make sure the type is indeed generic in which case the` is in the name

    int Index = TypeName.IndexOf("`");

    if (Index == -1)

        return TypeName;

 

    // *** Strip off the Genric postfix

    TypeName = TypeName.Substring(0, Index);

    string FormattedName = TypeName;

 

    // *** Parse the generic type arguments

    Type[] GenericArguments = GenericType.GetGenericArguments();

    string GenericOutput = "{";

    bool Start = true;

    foreach (Type GenericArg in GenericArguments)

    {

        if (Start)

        {

            if (GenericArg.IsGenericParameter)

                GenericOutput += "`" + GenericArg.GenericParameterPosition.ToString();

            else

                GenericOutput += GenericArg.Namespace + "." + GenericArg.Name ;

 

            Start = false;

        }

        else

            GenericOutput += "," + GenericArg.Name;

    }

 

    GenericOutput += "}";

    FormattedName += GenericOutput;

 

    return FormattedName;

}

 

Again, notice the use of GenericParameterPosition to figure out if a Generic parameter is passed to the Generic type. For example: List<MyGenericType> which results in List`1{`0}…

 

 

This is confusing as hell to figure out especially until you understand the GenericParameterPosition. Until I figured that one out I was scratching my head about how to map my members to the XML docs…

 

Ok, now that’s done for methods and clsas types and it’s working great pulling in the proper descriptive display and as well as the mapping signature that works with XML docs. Now I have some busy work to do and fit all of this to all the different member types (Properties, Fields, Events etc.)…

 

And don’t get me started on seeing what Visual Studio will do with it’s CodeDom signatures that I need to map for the two way tools to work…

 

Wish me luck…

 

 

Now playing: Prong – This is only a test

 


The Voices of Reason


 

Chris D
December 09, 2005

# re: Generics, Reflection and XML Documentation == Madness

First off, I really like your blog, been reading it for quite a while and of my list it's the one i always like seeing an update on the most. Posts are always colorful and full of personality.

Cool stuff in this post, nice to see how reflection over generics can be done.

Just thought i would let you know about a method I like to use for string concatenations with seperators. Rather than using a bool to keep track of whether this is the first iteration or not and having to check it every time, I like to append the seperator at the end on each iteration. Then as the last step you can use the TrimEnd() function to trim the trailing seperator.

ex:
foreach (Type GenericArg in GenericArguments)
{
GenericOutput += GenericArg.Name + ",";
}
GenericOutput = GenericOutput.TrimEnd(',');

cheers,
Chris D

Rick Strahl
December 09, 2005

# re: Generics, Reflection and XML Documentation == Madness

Hi Chris,

Thanks! Nice tip - never caught that TrimEnd() allows a char parm. Cool!

Liviu Uba
February 12, 2006

# ParameterType.IsgenericParameter = always false

Hi
I want to write a ILDASM clone for NET 2.0.
So far so good, I stuck into generics...

ParameterType.IsgenericParameter = always false
in VS 2005 Final.
What to do? I 've been looking for an answer for the last days and i would greatly appreciate a hint..

Thanks

# DotNetSlackers: Generics, Reflection and XML Documentation == Madness


Frank
April 27, 2007

# re: Generics, Reflection and XML Documentation == Madness

Is there a way to specify a generic.list in the web.config file? The angle brackets are invalid in an XML File. I can use `1 and it generates the angle brackets, but can't figure out how to get the type (string) between the brackets.
I'm trying to use a generic.list<string> in the profile. Code came from the MS quickstart tutorials. They don't show how the web.config entry looks.

From web.config:
<profile>
<properties>
<add name="JunkFood" type="System.Collections.Generic.List`1" />
</properties>
</profile>

From the profile entry page code behind:
<code lang="c#">
Profile.JunkFood = new List<String>();
foreach (ListItem item in lbxMunchies.Items)
{
if (item.Selected == true)
Profile.JunkFood.Add(item.Value);
}
</code&gt.

It errors if I use the angle brackets. If I use html entities it compiles but gets a 'could not load type' runtime error. The `1 gets a 'type expected' compile error, but produces the brackets.

Terraslate
June 27, 2007

# re: Generics, Reflection and XML Documentation == Madness

for one generic argument as below

GenericTypeName'1[[GenericArgumentTypeName]]

for 2 generic arguments

GenericTypeName'2[[GenericArgumentTypeName],[GenericArgumentTypeName]]

What i show above is the FullName of your type. You might still need to fully qualify your type to the assembly. Easist way is to just dump out the AssemblyQualifiedName of the generic type and copy/paste it into your web.config. Hence

System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]


hence

List'1[[System.String] is a good un.

Terraslate
June 27, 2007

# re: Generics, Reflection and XML Documentation == Madness

...or rather i meant hence

System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

the post above showing the fullname - this one the fully qualified name

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024